pub fn ln_gamma(x: f64) -> f64 {
if x <= 0.0 {
return f64::INFINITY;
}
const G: f64 = 7.0;
const COEF: [f64; 9] = [
0.999_999_999_999_809_9,
676.520_368_121_885_1,
-1_259.139_216_722_402_8,
771.323_428_777_653_1,
-176.615_029_162_140_6,
12.507_343_278_686_905,
-0.138_571_095_265_720_12,
9.984_369_578_019_572e-6,
1.505_632_735_149_311_6e-7,
];
if x < 0.5 {
let pi = std::f64::consts::PI;
return (pi / (pi * x).sin()).ln() - ln_gamma(1.0 - x);
}
let x = x - 1.0;
let mut a = COEF[0];
for (i, &c) in COEF.iter().enumerate().skip(1) {
a += c / (x + i as f64);
}
let t = x + G + 0.5;
0.5 * (2.0 * std::f64::consts::PI).ln() + (x + 0.5) * t.ln() - t + a.ln()
}
pub fn digamma(mut x: f64) -> f64 {
if x <= 0.0 {
return f64::NEG_INFINITY;
}
let mut result = 0.0;
while x < 6.0 {
result -= 1.0 / x;
x += 1.0;
}
let inv = 1.0 / x;
let inv2 = inv * inv;
result += x.ln()
- 0.5 * inv
- inv2 * (1.0 / 12.0 - inv2 * (1.0 / 120.0 - inv2 * (1.0 / 252.0 - inv2 / 240.0)));
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ln_gamma_matches_known_values() {
assert!((ln_gamma(1.0) - 0.0).abs() < 1e-10);
assert!((ln_gamma(2.0) - 0.0).abs() < 1e-10);
assert!((ln_gamma(3.0) - 2.0f64.ln()).abs() < 1e-10);
assert!((ln_gamma(0.5) - 0.5 * std::f64::consts::PI.ln()).abs() < 1e-10);
assert!((ln_gamma(10.0) - 362_880.0f64.ln()).abs() < 1e-8);
}
#[test]
fn digamma_matches_known_values() {
let euler_mascheroni = 0.577_215_664_901_532_9;
assert!((digamma(1.0) + euler_mascheroni).abs() < 1e-9);
assert!((digamma(2.0) - (1.0 - euler_mascheroni)).abs() < 1e-9);
let expected = -euler_mascheroni - 2.0 * 2.0f64.ln();
assert!((digamma(0.5) - expected).abs() < 1e-9);
}
#[test]
fn digamma_recurrence() {
for &x in &[0.3, 1.7, 3.2, 10.5] {
let lhs = digamma(x + 1.0);
let rhs = digamma(x) + 1.0 / x;
assert!((lhs - rhs).abs() < 1e-9, "x = {}", x);
}
}
}