patternrustModerate
Gamma function in Rust
Viewed 0 times
rustfunctiongamma
Problem
The gamma function is one of a couple nice continuous extensions to the traditional factorial function. I used this Python program as a reference, which in turn, uses this Ada program. As the Ada program describes:
The implementation uses Taylor series coefficients of $$Γ(x+1)^{-1}, |x| < \infty$$ The coefficients are taken from Mathematical functions and their approximations by Yudell L. Luke.
Here is the Rust translation (Rust Playground):
Any suggestions on making this code better such as making the code more Rust-idiomatic would be appreciated.
The implementation uses Taylor series coefficients of $$Γ(x+1)^{-1}, |x| < \infty$$ The coefficients are taken from Mathematical functions and their approximations by Yudell L. Luke.
Here is the Rust translation (Rust Playground):
fn gamma(x: f64) -> f64 {
let _a: [f64; 29] = [ 1.00000000000000000000, 0.57721566490153286061, -0.65587807152025388108,
-0.04200263503409523553, 0.16653861138229148950, -0.04219773455554433675,
-0.00962197152787697356, 0.00721894324666309954, -0.00116516759185906511,
-0.00021524167411495097, 0.00012805028238811619, -0.00002013485478078824,
-0.00000125049348214267, 0.00000113302723198170, -0.00000020563384169776,
0.00000000611609510448, 0.00000000500200764447, -0.00000000118127457049,
0.00000000010434267117, 0.00000000000778226344, -0.00000000000369680562,
0.00000000000051003703, -0.00000000000002058326, -0.00000000000000534812,
0.00000000000000122678, -0.00000000000000011813, 0.00000000000000000119,
0.00000000000000000141, -0.00000000000000000023 ];
let mut sm: f64 = 0.00000000000000000002;
for an in _a.iter().rev() {
sm = sm * (x - 1.0) + an;
}
1.0 / sm
}
fn main() {
for i in 1..11 {
let f = i as f64;
println!("{}", gamma(f/3.0));
}
}Any suggestions on making this code better such as making the code more Rust-idiomatic would be appreciated.
Solution
There's not a lot of code here so there's not a lot to say. ^_^
Notes based on the original Ada implementation:
- Implementations like this are generally outside of the day-to-day knowledge of most programmers. You may wish to include a reference in the code to how the algorithm was derived and how it works.
- The chosen variable names are pretty useless. I have no idea what
anorsmmean. Because I don't know what they mean, I didn't change them, but you should. (Looking back at the Ada implementation, I see thatsmshould besumandanis just the current value of the lookup table, but it shouldn't be required to find 2 other implementations to understand this one).
- Leading underscores in a variable name means the variable is unused (but cannot be removed for some reason). Don't use a underscore and use the variable at the same time.
- If something is a constant, make it so. Constants use
constorstaticand are named withSCREAMING_SNAKE_CASE.
- There is inconsistent alignment in the lookup table. The first column is aligned on the decimal point, the second and third columns are not.
- The
smvariable doesn't need an explicit type as type inference will cover it.
- There are inplace operations like
=which are shorter thanfoo = foo bar.
- There's a
f64::recipmethod which may be more intuitive.
- Operators like
/should have space on both sides.
- Is there a benefit to iterating in reverse every time? I'd just reorder the table and iterate forwards. Computers generally like going forwards.
- The loop can be simplified by using
Iterator::fold.
const TAYLOR_COEFFICIENTS: [f64; 29] = [
-0.00000000000000000023, 0.00000000000000000141, 0.00000000000000000119,
-0.00000000000000011813, 0.00000000000000122678, -0.00000000000000534812,
-0.00000000000002058326, 0.00000000000051003703, -0.00000000000369680562,
0.00000000000778226344, 0.00000000010434267117, -0.00000000118127457049,
0.00000000500200764447, 0.00000000611609510448, -0.00000020563384169776,
0.00000113302723198170, -0.00000125049348214267, -0.00002013485478078824,
0.00012805028238811619, -0.00021524167411495097, -0.00116516759185906511,
0.00721894324666309954, -0.00962197152787697356, -0.04219773455554433675,
0.16653861138229148950, -0.04200263503409523553, -0.65587807152025388108,
0.57721566490153286061, 1.00000000000000000000,
];
const INITIAL_SUM: f64 = 0.00000000000000000002;
fn gamma(x: f64) -> f64 {
TAYLOR_COEFFICIENTS.iter().fold(INITIAL_SUM, |sum, coefficient| {
sum * (x - 1.0) + coefficient
}).recip()
}
fn main() {
for i in 1..11 {
let f = i as f64;
println!("{}", gamma(f / 3.0));
}
}Notes based on the original Ada implementation:
- The lookup table was defined inside the
gammafunction, and the same could be done here. It's a matter of personal preference at this point in time.
- Long numbers could be separated with underscores:
0.111_222_333.
- The loop invariant
1.0 - xcould be hoisted out of the loop, but I'd trust the optimizer to do that until proven otherwise.
Code Snippets
const TAYLOR_COEFFICIENTS: [f64; 29] = [
-0.00000000000000000023, 0.00000000000000000141, 0.00000000000000000119,
-0.00000000000000011813, 0.00000000000000122678, -0.00000000000000534812,
-0.00000000000002058326, 0.00000000000051003703, -0.00000000000369680562,
0.00000000000778226344, 0.00000000010434267117, -0.00000000118127457049,
0.00000000500200764447, 0.00000000611609510448, -0.00000020563384169776,
0.00000113302723198170, -0.00000125049348214267, -0.00002013485478078824,
0.00012805028238811619, -0.00021524167411495097, -0.00116516759185906511,
0.00721894324666309954, -0.00962197152787697356, -0.04219773455554433675,
0.16653861138229148950, -0.04200263503409523553, -0.65587807152025388108,
0.57721566490153286061, 1.00000000000000000000,
];
const INITIAL_SUM: f64 = 0.00000000000000000002;
fn gamma(x: f64) -> f64 {
TAYLOR_COEFFICIENTS.iter().fold(INITIAL_SUM, |sum, coefficient| {
sum * (x - 1.0) + coefficient
}).recip()
}
fn main() {
for i in 1..11 {
let f = i as f64;
println!("{}", gamma(f / 3.0));
}
}Context
StackExchange Code Review Q#116850, answer score: 10
Revisions (0)
No revisions yet.