mathhook_core/functions/special/
factorial.rs

1//! Factorial function implementation
2
3use crate::core::{Expression, Number};
4use num_bigint::BigInt;
5use num_traits::One;
6
7/// Factorial function n!
8///
9/// # Mathematical Definition
10///
11/// n! = n × (n-1) × (n-2) × ... × 2 × 1
12/// 0! = 1 by convention
13///
14/// # Arguments
15///
16/// * `arg` - Expression to compute factorial of
17///
18/// # Returns
19///
20/// Factorial expression
21///
22/// # Examples
23///
24/// ```
25/// use mathhook_core::functions::special::factorial::factorial;
26/// use mathhook_core::{expr, Expression};
27///
28/// // Factorial returns BigInteger, so we check it's a number
29/// let result = factorial(&Expression::integer(0));
30/// assert!(matches!(result, Expression::Number(_)));
31///
32/// let result2 = factorial(&Expression::integer(5));
33/// assert!(matches!(result2, Expression::Number(_)));
34/// ```
35pub fn factorial(arg: &Expression) -> Expression {
36    match arg {
37        Expression::Number(Number::Integer(n)) if *n >= 0 => {
38            Expression::Number(compute_factorial(*n))
39        }
40        Expression::Number(Number::Integer(n)) if *n < 0 => {
41            Expression::function("factorial", vec![arg.clone()])
42        }
43        _ => Expression::function("factorial", vec![arg.clone()]),
44    }
45}
46
47/// Compute factorial, returning Integer if it fits in i64, BigInteger otherwise
48fn compute_factorial(n: i64) -> Number {
49    if n == 0 || n == 1 {
50        return Number::Integer(1);
51    }
52
53    // Try to compute as i64 first (checked arithmetic)
54    let mut result: i64 = 1;
55    for i in 2..=n {
56        match result.checked_mul(i) {
57            Some(new_result) => result = new_result,
58            None => {
59                // Overflow - fall back to BigInt computation
60                return Number::BigInteger(Box::new(compute_factorial_bigint(n)));
61            }
62        }
63    }
64    Number::Integer(result)
65}
66
67/// Compute factorial as BigInt (for large values)
68fn compute_factorial_bigint(n: i64) -> BigInt {
69    if n == 0 || n == 1 {
70        return BigInt::one();
71    }
72
73    let mut result = BigInt::one();
74    for i in 2..=n {
75        result *= BigInt::from(i);
76    }
77    result
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_factorial_zero() {
86        let result = factorial(&Expression::integer(0));
87        assert_eq!(result, Expression::Number(Number::Integer(1)));
88    }
89
90    #[test]
91    fn test_factorial_one() {
92        let result = factorial(&Expression::integer(1));
93        assert_eq!(result, Expression::Number(Number::Integer(1)));
94    }
95
96    #[test]
97    fn test_factorial_five() {
98        let result = factorial(&Expression::integer(5));
99        assert_eq!(result, Expression::Number(Number::Integer(120)));
100    }
101
102    #[test]
103    fn test_factorial_ten() {
104        let result = factorial(&Expression::integer(10));
105        assert_eq!(result, Expression::Number(Number::Integer(3628800)));
106    }
107
108    #[test]
109    fn test_factorial_large() {
110        // 21! doesn't fit in i64, should return BigInteger
111        let result = factorial(&Expression::integer(21));
112        assert!(matches!(result, Expression::Number(Number::BigInteger(_))));
113
114        if let Expression::Number(Number::BigInteger(bi)) = result {
115            // 21! = 51090942171709440000
116            let expected: BigInt = "51090942171709440000".parse().unwrap();
117            assert_eq!(*bi, expected);
118        }
119    }
120}