#![allow(clippy::excessive_precision)]
use std::f64::consts::PI;
use crate::errors::QlResult;
use crate::fail;
use crate::types::Real;
const COEFFS: [Real; 6] = [
76.18009172947146,
-86.50532032941677,
24.01409824083091,
-1.231739572450155,
0.1208650973866179e-2,
-0.5395239384953e-5,
];
const SQRT_TWO_PI: Real = 2.5066282746310005;
pub fn log_gamma(x: Real) -> QlResult<Real> {
if !x.is_finite() || x <= 0.0 {
fail!("log_gamma requires a finite positive argument, got {x}");
}
let mut temp = x + 5.5;
temp -= (x + 0.5) * temp.ln();
let mut ser = 1.000000000190015;
for (i, &c) in COEFFS.iter().enumerate() {
ser += c / (x + (i as Real + 1.0));
}
Ok(-temp + (SQRT_TWO_PI * ser / x).ln())
}
pub fn gamma(x: Real) -> Real {
if !x.is_finite() {
return if x > 0.0 { Real::INFINITY } else { Real::NAN };
}
if x >= 1.0 {
log_gamma(x).expect("log_gamma is valid for x >= 1").exp()
} else if x > -20.0 {
gamma(x + 1.0) / x
} else {
-PI / (gamma(-x) * x * (PI * x).sin())
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: Real = 1e-9;
fn assert_close(got: Real, expected: Real) {
let tol = TOL * (1.0 + expected.abs());
assert!(
(got - expected).abs() <= tol,
"got {got}, expected {expected}, diff {}",
(got - expected).abs()
);
}
#[test]
fn matches_known_values() {
assert_close(log_gamma(1.0).unwrap(), 0.0);
assert_close(log_gamma(2.0).unwrap(), 0.0);
assert_close(log_gamma(0.5).unwrap(), 0.572_364_942_924_700_1);
assert_close(log_gamma(5.0).unwrap(), 3.178_053_830_347_945_8); assert_close(log_gamma(10.0).unwrap(), 12.801_827_480_081_469); }
#[test]
fn small_and_large_arguments() {
assert_close(log_gamma(0.1).unwrap(), 2.252_712_651_734_206);
assert_close(log_gamma(100.0).unwrap(), 359.134_205_369_575_4); }
#[test]
fn recurrence_ln_gamma_x_plus_1() {
for &x in &[0.3, 1.7, 4.2, 9.9] {
let lhs = log_gamma(x + 1.0).unwrap();
let rhs = log_gamma(x).unwrap() + x.ln();
assert!((lhs - rhs).abs() < 1e-8, "recurrence failed at {x}");
}
}
#[test]
fn nonpositive_or_nan_argument_is_rejected() {
assert!(log_gamma(0.0).is_err());
assert!(log_gamma(-1.0).is_err());
assert!(log_gamma(Real::NAN).is_err());
assert!(log_gamma(Real::INFINITY).is_err());
assert!(log_gamma(Real::NEG_INFINITY).is_err());
}
#[test]
fn matches_cumulative_log_factorial_through_9000() {
assert!(log_gamma(1.0).unwrap().abs() <= 1e-15);
let mut expected = 0.0;
for i in 2..9000 {
expected += Real::from(i).ln();
let calculated = log_gamma(Real::from(i + 1)).unwrap();
assert!(
(calculated - expected).abs() / expected <= 1e-9,
"log_gamma({}) rel err {}",
i + 1,
(calculated - expected).abs() / expected
);
}
}
#[test]
fn value_matches_reference_table() {
let tasks: [(Real, Real, Real); 10] = [
(0.0001, 9999.422883231624, 1e3),
(1.2, 0.9181687423997607, 1e3),
(7.3, 1271.4236336639089586, 1e3),
(-1.1, 9.7148063829028946, 1e3),
(-4.001, -41.6040228304425312, 1e3),
(-4.999, -8.347576090315059, 1e3),
(-19.000001, 8.220610833201313e-12, 1e8),
(-19.5, 5.811045977502255e-18, 1e3),
(-21.000001, 1.957288098276488e-14, 1e8),
(-21.5, 1.318444918321553e-20, 1e6),
];
for (x, expected, multiplier) in tasks {
let calculated = gamma(x);
let tol = multiplier * Real::EPSILON * expected.abs();
assert!(
(calculated - expected).abs() <= tol,
"Γ({x}): got {calculated}, expected {expected}, diff {}, tol {tol}",
(calculated - expected).abs()
);
}
}
#[test]
fn value_handles_poles_and_nan() {
assert!(gamma(0.0).is_infinite());
assert!(gamma(-1.0).is_infinite());
assert!(gamma(-19.0).is_infinite());
assert!(gamma(-20.0).is_finite());
assert!(gamma(Real::NAN).is_nan());
}
#[test]
fn value_at_infinities_does_not_panic() {
assert_eq!(gamma(Real::INFINITY), Real::INFINITY);
assert!(gamma(Real::NEG_INFINITY).is_nan());
}
}