use crate::core::{Expression, Number};
use std::f64::consts::PI;
pub fn gamma(z: &Expression) -> Expression {
match z {
Expression::Number(Number::Integer(n)) if *n > 0 => {
let val = *n;
if val == 1 {
Expression::Number(Number::Integer(1))
} else {
let mut result = 1i64;
for i in 1..val {
result *= i;
}
Expression::Number(Number::Integer(result))
}
}
Expression::Number(Number::Float(x)) => {
let twice = x * 2.0;
if (twice - twice.round()).abs() < 1e-10 {
gamma_half_integer(*x)
} else {
let result = lanczos_gamma(*x);
Expression::Number(Number::Float(result))
}
}
_ => Expression::function("gamma", vec![z.clone()]),
}
}
fn gamma_half_integer(x: f64) -> Expression {
let n = (x - 0.5).round() as i64;
if (x - (n as f64 + 0.5)).abs() < 1e-10 && n >= 0 {
let sqrt_pi = Expression::sqrt(Expression::pi());
if n == 0 {
return sqrt_pi;
}
let mut double_fact = Expression::integer(1);
for k in 0..n {
let term = Expression::integer(2 * k + 1);
double_fact = Expression::mul(vec![double_fact, term]);
}
let numerator = Expression::mul(vec![double_fact, sqrt_pi]);
let denominator = Expression::pow(Expression::integer(2), Expression::integer(n));
Expression::div(numerator, denominator)
} else {
Expression::Number(Number::Float(lanczos_gamma(x)))
}
}
pub fn lanczos_gamma(z: f64) -> f64 {
if z.is_nan() || z.is_infinite() {
return f64::NAN;
}
if z <= 0.0 && (z - z.round()).abs() < 1e-10 {
return f64::INFINITY;
}
const LANCZOS_G: f64 = 7.0;
const LANCZOS_COEFFS: [f64; 9] = [
0.999_999_999_999_809_9,
676.5203681218851,
-1259.1392167224028,
771.323_428_777_653_1,
-176.615_029_162_140_6,
12.507343278686905,
-0.13857109526572012,
9.984_369_578_019_572e-6,
1.5056327351493116e-7,
];
if z < 0.5 {
PI / (f64::sin(PI * z) * lanczos_gamma(1.0 - z))
} else {
let z = z - 1.0;
let mut x = LANCZOS_COEFFS[0];
for (i, coef) in LANCZOS_COEFFS.iter().enumerate().skip(1) {
x += coef / (z + i as f64);
}
let t = z + LANCZOS_G + 0.5;
f64::sqrt(2.0 * PI) * f64::powf(t, z + 0.5) * f64::exp(-t) * x
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamma_positive_integers() {
assert_eq!(
gamma(&Expression::Number(Number::Integer(1))),
Expression::Number(Number::Integer(1))
);
assert_eq!(
gamma(&Expression::Number(Number::Integer(2))),
Expression::Number(Number::Integer(1))
);
assert_eq!(
gamma(&Expression::Number(Number::Integer(3))),
Expression::Number(Number::Integer(2))
);
assert_eq!(
gamma(&Expression::Number(Number::Integer(4))),
Expression::Number(Number::Integer(6))
);
assert_eq!(
gamma(&Expression::Number(Number::Integer(5))),
Expression::Number(Number::Integer(24))
);
}
#[test]
fn test_lanczos_gamma_numerical() {
let result = lanczos_gamma(5.0);
assert!((result - 24.0).abs() < 1e-10);
}
#[test]
fn test_lanczos_gamma_accuracy() {
let result_half = lanczos_gamma(0.5);
let expected_half = std::f64::consts::PI.sqrt();
assert!(
(result_half - expected_half).abs() < 1e-14,
"Γ(1/2) accuracy: expected {}, got {}",
expected_half,
result_half
);
let result_one = lanczos_gamma(1.0);
assert!((result_one - 1.0).abs() < 1e-14, "Γ(1) = 1");
let result_two = lanczos_gamma(2.0);
assert!((result_two - 1.0).abs() < 1e-14, "Γ(2) = 1");
let result_three = lanczos_gamma(3.0);
assert!((result_three - 2.0).abs() < 1e-14, "Γ(3) = 2");
}
#[test]
fn test_gamma_half_integer_symbolic() {
let result = gamma(&Expression::Number(Number::Float(0.5)));
let expected = Expression::sqrt(Expression::pi());
assert_eq!(result, expected, "Γ(1/2) should be √π symbolically");
let result_1_5 = gamma(&Expression::Number(Number::Float(1.5)));
let sqrt_pi = Expression::sqrt(Expression::pi());
let expected_1_5 = Expression::div(sqrt_pi, Expression::integer(2));
assert_eq!(
result_1_5, expected_1_5,
"Γ(3/2) should be √π/2 symbolically"
);
}
#[test]
fn test_gamma_float_numerical() {
let result = gamma(&Expression::Number(Number::Float(3.7)));
match result {
Expression::Number(Number::Float(_)) => {}
_ => panic!("Γ(3.7) should return numerical Float"),
}
}
#[test]
fn test_lanczos_gamma_input_validation() {
assert!(lanczos_gamma(f64::NAN).is_nan());
assert!(lanczos_gamma(f64::INFINITY).is_nan());
assert!(lanczos_gamma(0.0).is_infinite());
assert!(lanczos_gamma(-1.0).is_infinite());
assert!(lanczos_gamma(-2.0).is_infinite());
}
}