mathhook_core/functions/special/
gamma.rs1use crate::core::{Expression, Number};
40use std::f64::consts::PI;
41
42pub fn gamma(z: &Expression) -> Expression {
80 match z {
81 Expression::Number(Number::Integer(n)) if *n > 0 => {
82 let val = *n;
83 if val == 1 {
84 Expression::Number(Number::Integer(1))
85 } else {
86 let mut result = 1i64;
87 for i in 1..val {
88 result *= i;
89 }
90 Expression::Number(Number::Integer(result))
91 }
92 }
93 Expression::Number(Number::Float(x)) => {
94 let twice = x * 2.0;
95 if (twice - twice.round()).abs() < 1e-10 {
96 gamma_half_integer(*x)
97 } else {
98 let result = lanczos_gamma(*x);
99 Expression::Number(Number::Float(result))
100 }
101 }
102 _ => Expression::function("gamma", vec![z.clone()]),
103 }
104}
105
106fn gamma_half_integer(x: f64) -> Expression {
114 let n = (x - 0.5).round() as i64;
115 if (x - (n as f64 + 0.5)).abs() < 1e-10 && n >= 0 {
116 let sqrt_pi = Expression::sqrt(Expression::pi());
117 if n == 0 {
118 return sqrt_pi;
119 }
120 let mut double_fact = Expression::integer(1);
121 for k in 0..n {
122 let term = Expression::integer(2 * k + 1);
123 double_fact = Expression::mul(vec![double_fact, term]);
124 }
125 let numerator = Expression::mul(vec![double_fact, sqrt_pi]);
126 let denominator = Expression::pow(Expression::integer(2), Expression::integer(n));
127 Expression::div(numerator, denominator)
128 } else {
129 Expression::Number(Number::Float(lanczos_gamma(x)))
130 }
131}
132
133pub fn lanczos_gamma(z: f64) -> f64 {
156 if z.is_nan() || z.is_infinite() {
157 return f64::NAN;
158 }
159 if z <= 0.0 && (z - z.round()).abs() < 1e-10 {
160 return f64::INFINITY;
161 }
162 const LANCZOS_G: f64 = 7.0;
163 const LANCZOS_COEFFS: [f64; 9] = [
164 0.999_999_999_999_809_9,
165 676.5203681218851,
166 -1259.1392167224028,
167 771.323_428_777_653_1,
168 -176.615_029_162_140_6,
169 12.507343278686905,
170 -0.13857109526572012,
171 9.984_369_578_019_572e-6,
172 1.5056327351493116e-7,
173 ];
174 if z < 0.5 {
175 PI / (f64::sin(PI * z) * lanczos_gamma(1.0 - z))
176 } else {
177 let z = z - 1.0;
178 let mut x = LANCZOS_COEFFS[0];
179 for (i, coef) in LANCZOS_COEFFS.iter().enumerate().skip(1) {
180 x += coef / (z + i as f64);
181 }
182 let t = z + LANCZOS_G + 0.5;
183 f64::sqrt(2.0 * PI) * f64::powf(t, z + 0.5) * f64::exp(-t) * x
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_gamma_positive_integers() {
193 assert_eq!(
194 gamma(&Expression::Number(Number::Integer(1))),
195 Expression::Number(Number::Integer(1))
196 );
197 assert_eq!(
198 gamma(&Expression::Number(Number::Integer(2))),
199 Expression::Number(Number::Integer(1))
200 );
201 assert_eq!(
202 gamma(&Expression::Number(Number::Integer(3))),
203 Expression::Number(Number::Integer(2))
204 );
205 assert_eq!(
206 gamma(&Expression::Number(Number::Integer(4))),
207 Expression::Number(Number::Integer(6))
208 );
209 assert_eq!(
210 gamma(&Expression::Number(Number::Integer(5))),
211 Expression::Number(Number::Integer(24))
212 );
213 }
214
215 #[test]
216 fn test_lanczos_gamma_numerical() {
217 let result = lanczos_gamma(5.0);
218 assert!((result - 24.0).abs() < 1e-10);
219 }
220
221 #[test]
222 fn test_lanczos_gamma_accuracy() {
223 let result_half = lanczos_gamma(0.5);
224 let expected_half = std::f64::consts::PI.sqrt();
225 assert!(
226 (result_half - expected_half).abs() < 1e-14,
227 "Γ(1/2) accuracy: expected {}, got {}",
228 expected_half,
229 result_half
230 );
231 let result_one = lanczos_gamma(1.0);
232 assert!((result_one - 1.0).abs() < 1e-14, "Γ(1) = 1");
233 let result_two = lanczos_gamma(2.0);
234 assert!((result_two - 1.0).abs() < 1e-14, "Γ(2) = 1");
235 let result_three = lanczos_gamma(3.0);
236 assert!((result_three - 2.0).abs() < 1e-14, "Γ(3) = 2");
237 }
238
239 #[test]
240 fn test_gamma_half_integer_symbolic() {
241 let result = gamma(&Expression::Number(Number::Float(0.5)));
242 let expected = Expression::sqrt(Expression::pi());
243 assert_eq!(result, expected, "Γ(1/2) should be √π symbolically");
244 let result_1_5 = gamma(&Expression::Number(Number::Float(1.5)));
245 let sqrt_pi = Expression::sqrt(Expression::pi());
246 let expected_1_5 = Expression::div(sqrt_pi, Expression::integer(2));
247 assert_eq!(
248 result_1_5, expected_1_5,
249 "Γ(3/2) should be √π/2 symbolically"
250 );
251 }
252
253 #[test]
254 fn test_gamma_float_numerical() {
255 let result = gamma(&Expression::Number(Number::Float(3.7)));
256 match result {
257 Expression::Number(Number::Float(_)) => {}
258 _ => panic!("Γ(3.7) should return numerical Float"),
259 }
260 }
261
262 #[test]
263 fn test_lanczos_gamma_input_validation() {
264 assert!(lanczos_gamma(f64::NAN).is_nan());
265 assert!(lanczos_gamma(f64::INFINITY).is_nan());
266 assert!(lanczos_gamma(0.0).is_infinite());
267 assert!(lanczos_gamma(-1.0).is_infinite());
268 assert!(lanczos_gamma(-2.0).is_infinite());
269 }
270}