Skip to main content

rust_decimal/
maths.rs

1use crate::prelude::*;
2use num_traits::pow::Pow;
3
4// Approximation of 1/ln(10) = 0.4342944819032518276511289189
5const LN10_INVERSE: Decimal = Decimal::from_parts_raw(1763037029, 1670682625, 235431510, 1835008);
6// PI / 8
7const EIGHTH_PI: Decimal = Decimal::from_parts_raw(2822163429, 3244459792, 212882598, 1835008);
8
9// Table representing {index}! — used in tests to verify factorial values.
10#[cfg(test)]
11const FACTORIAL: [Decimal; 28] = [
12    Decimal::from_parts(1, 0, 0, false, 0),
13    Decimal::from_parts(1, 0, 0, false, 0),
14    Decimal::from_parts(2, 0, 0, false, 0),
15    Decimal::from_parts(6, 0, 0, false, 0),
16    Decimal::from_parts(24, 0, 0, false, 0),
17    // 5!
18    Decimal::from_parts(120, 0, 0, false, 0),
19    Decimal::from_parts(720, 0, 0, false, 0),
20    Decimal::from_parts(5040, 0, 0, false, 0),
21    Decimal::from_parts(40320, 0, 0, false, 0),
22    Decimal::from_parts(362880, 0, 0, false, 0),
23    // 10!
24    Decimal::from_parts(3628800, 0, 0, false, 0),
25    Decimal::from_parts(39916800, 0, 0, false, 0),
26    Decimal::from_parts(479001600, 0, 0, false, 0),
27    Decimal::from_parts(1932053504, 1, 0, false, 0),
28    Decimal::from_parts(1278945280, 20, 0, false, 0),
29    // 15!
30    Decimal::from_parts(2004310016, 304, 0, false, 0),
31    Decimal::from_parts(2004189184, 4871, 0, false, 0),
32    Decimal::from_parts(4006445056, 82814, 0, false, 0),
33    Decimal::from_parts(3396534272, 1490668, 0, false, 0),
34    Decimal::from_parts(109641728, 28322707, 0, false, 0),
35    // 20!
36    Decimal::from_parts(2192834560, 566454140, 0, false, 0),
37    Decimal::from_parts(3099852800, 3305602358, 2, false, 0),
38    Decimal::from_parts(3772252160, 4003775155, 60, false, 0),
39    Decimal::from_parts(862453760, 1892515369, 1401, false, 0),
40    Decimal::from_parts(3519021056, 2470695900, 33634, false, 0),
41    // 25!
42    Decimal::from_parts(2076180480, 1637855376, 840864, false, 0),
43    Decimal::from_parts(2441084928, 3929534124, 21862473, false, 0),
44    Decimal::from_parts(1484783616, 3018206259, 590286795, false, 0),
45];
46
47/// Trait exposing various mathematical operations that can be applied using a Decimal. This is only
48/// present when the `maths` feature has been enabled, e.g. by adding the crate with
49// `cargo add rust_decimal --features maths` and importing in your Rust file with `use rust_decimal::MathematicalOps;`
50pub trait MathematicalOps {
51    /// The estimated exponential function, e<sup>x</sup>. Stops calculating when it is within
52    /// tolerance of roughly `0.0000002`.
53    fn exp(&self) -> Decimal;
54
55    /// The estimated exponential function, e<sup>x</sup>. Stops calculating when it is within
56    /// tolerance of roughly `0.0000002`. Returns `None` on overflow.
57    fn checked_exp(&self) -> Option<Decimal>;
58
59    /// The estimated exponential function, e<sup>x</sup> using the `tolerance` provided as a hint
60    /// as to when to stop calculating. A larger tolerance will cause the number to stop calculating
61    /// sooner at the potential cost of a slightly less accurate result.
62    fn exp_with_tolerance(&self, tolerance: Decimal) -> Decimal;
63
64    /// The estimated exponential function, e<sup>x</sup> using the `tolerance` provided as a hint
65    /// as to when to stop calculating. A larger tolerance will cause the number to stop calculating
66    /// sooner at the potential cost of a slightly less accurate result.
67    /// Returns `None` on overflow.
68    fn checked_exp_with_tolerance(&self, tolerance: Decimal) -> Option<Decimal>;
69
70    /// Raise self to the given integer exponent: x<sup>y</sup>
71    fn powi(&self, exp: i64) -> Decimal;
72
73    /// Raise self to the given integer exponent x<sup>y</sup> returning `None` on overflow.
74    fn checked_powi(&self, exp: i64) -> Option<Decimal>;
75
76    /// Raise self to the given unsigned integer exponent: x<sup>y</sup>
77    fn powu(&self, exp: u64) -> Decimal;
78
79    /// Raise self to the given unsigned integer exponent x<sup>y</sup> returning `None` on overflow.
80    fn checked_powu(&self, exp: u64) -> Option<Decimal>;
81
82    /// Raise self to the given floating point exponent: x<sup>y</sup>
83    fn powf(&self, exp: f64) -> Decimal;
84
85    /// Raise self to the given floating point exponent x<sup>y</sup> returning `None` on overflow.
86    fn checked_powf(&self, exp: f64) -> Option<Decimal>;
87
88    /// Raise self to the given Decimal exponent: x<sup>y</sup>. If `exp` is not whole then the approximation
89    /// e<sup>y*ln(x)</sup> is used.
90    fn powd(&self, exp: Decimal) -> Decimal;
91
92    /// Raise self to the given Decimal exponent x<sup>y</sup> returning `None` on overflow.
93    /// If `exp` is not whole then the approximation e<sup>y*ln(x)</sup> is used.
94    fn checked_powd(&self, exp: Decimal) -> Option<Decimal>;
95
96    /// The square root of a Decimal. Uses a standard Babylonian method.
97    fn sqrt(&self) -> Option<Decimal>;
98
99    /// Calculates the natural logarithm for a Decimal calculated using Taylor's series.
100    fn ln(&self) -> Decimal;
101
102    /// Calculates the checked natural logarithm for a Decimal calculated using Taylor's series.
103    /// Returns `None` for negative numbers or zero.
104    fn checked_ln(&self) -> Option<Decimal>;
105
106    /// Calculates the base 10 logarithm of a specified Decimal number.
107    fn log10(&self) -> Decimal;
108
109    /// Calculates the checked base 10 logarithm of a specified Decimal number.
110    /// Returns `None` for negative numbers or zero.
111    fn checked_log10(&self) -> Option<Decimal>;
112
113    /// Abramowitz Approximation of Error Function from [wikipedia](https://en.wikipedia.org/wiki/Error_function#Numerical_approximations)
114    fn erf(&self) -> Decimal;
115
116    /// The Cumulative distribution function for a Normal distribution
117    fn norm_cdf(&self) -> Decimal;
118
119    /// The Probability density function for a Normal distribution.
120    fn norm_pdf(&self) -> Decimal;
121
122    /// The Probability density function for a Normal distribution returning `None` on overflow.
123    fn checked_norm_pdf(&self) -> Option<Decimal>;
124
125    /// Computes the sine of a number (in radians).
126    /// Panics upon overflow.
127    fn sin(&self) -> Decimal;
128
129    /// Computes the checked sine of a number (in radians).
130    fn checked_sin(&self) -> Option<Decimal>;
131
132    /// Computes the cosine of a number (in radians).
133    /// Panics upon overflow.
134    fn cos(&self) -> Decimal;
135
136    /// Computes the checked cosine of a number (in radians).
137    fn checked_cos(&self) -> Option<Decimal>;
138
139    /// Computes the tangent of a number (in radians).
140    /// Panics upon overflow or upon approaching a limit.
141    fn tan(&self) -> Decimal;
142
143    /// Computes the checked tangent of a number (in radians).
144    /// Returns None on limit.
145    fn checked_tan(&self) -> Option<Decimal>;
146}
147
148impl MathematicalOps for Decimal {
149    fn exp(&self) -> Decimal {
150        match self.checked_exp() {
151            Some(d) => d,
152            None => {
153                if self.is_sign_negative() {
154                    panic!("Exp underflowed")
155                } else {
156                    panic!("Exp overflowed")
157                }
158            }
159        }
160    }
161
162    fn checked_exp(&self) -> Option<Decimal> {
163        crate::ops::wide::exp_wide(self)
164    }
165
166    fn exp_with_tolerance(&self, tolerance: Decimal) -> Decimal {
167        match self.checked_exp_with_tolerance(tolerance) {
168            Some(d) => d,
169            None => {
170                if self.is_sign_negative() {
171                    panic!("Exp underflowed")
172                } else {
173                    panic!("Exp overflowed")
174                }
175            }
176        }
177    }
178
179    fn checked_exp_with_tolerance(&self, tolerance: Decimal) -> Option<Decimal> {
180        if self.is_zero() {
181            return Some(Decimal::ONE);
182        }
183        if self.is_sign_negative() {
184            let mut flipped = *self;
185            flipped.set_sign_positive(true);
186            let exp = flipped.checked_exp_with_tolerance(tolerance)?;
187            return Decimal::ONE.checked_div(exp);
188        }
189
190        // exp(x) = sum_i x^i / i!, let q_i := x^i/i!
191        // Avoid computing x^i directly as it will quickly outgrow exp(x) for x > 1.
192        // Instead we compute q_i = x*(x/2)*(x/3)*...*(x/i)
193
194        let mut result = self.checked_add(Decimal::ONE)?;
195        let mut term = *self;
196
197        for i in 2..200u32 {
198            let i_dec = Decimal::from_u32(i).unwrap();
199            term = self.checked_mul(term.checked_div(i_dec)?)?;
200            result = result.checked_add(term)?;
201            if term <= tolerance {
202                break;
203            }
204        }
205
206        Some(result)
207    }
208
209    fn powi(&self, exp: i64) -> Decimal {
210        match self.checked_powi(exp) {
211            Some(result) => result,
212            None => panic!("Pow overflowed"),
213        }
214    }
215
216    fn checked_powi(&self, exp: i64) -> Option<Decimal> {
217        // For negative exponents we change x^-y into 1 / x^y.
218        // Otherwise, we calculate a standard unsigned exponent
219        if exp >= 0 {
220            return self.checked_powu(exp as u64);
221        }
222
223        // Get the unsigned exponent
224        let exp = exp.unsigned_abs();
225        let pow = self.checked_powu(exp)?;
226        Decimal::ONE.checked_div(pow)
227    }
228
229    fn powu(&self, exp: u64) -> Decimal {
230        match self.checked_powu(exp) {
231            Some(result) => result,
232            None => panic!("Pow overflowed"),
233        }
234    }
235
236    fn checked_powu(&self, exp: u64) -> Option<Decimal> {
237        crate::ops::wide::powu_wide(self, exp)
238    }
239
240    fn powf(&self, exp: f64) -> Decimal {
241        match self.checked_powf(exp) {
242            Some(result) => result,
243            None => panic!("Pow overflowed"),
244        }
245    }
246
247    fn checked_powf(&self, exp: f64) -> Option<Decimal> {
248        let exp = Decimal::from_f64(exp)?;
249        self.checked_powd(exp)
250    }
251
252    fn powd(&self, exp: Decimal) -> Decimal {
253        match self.checked_powd(exp) {
254            Some(result) => result,
255            None => panic!("Pow overflowed"),
256        }
257    }
258
259    fn checked_powd(&self, exp: Decimal) -> Option<Decimal> {
260        if exp.is_zero() {
261            return Some(Decimal::ONE);
262        }
263        if self.is_zero() {
264            return Some(Decimal::ZERO);
265        }
266        if self.is_one() {
267            return Some(Decimal::ONE);
268        }
269        if exp.is_one() {
270            return Some(*self);
271        }
272
273        // If the scale is 0 then it's a trivial calculation
274        let exp = exp.normalize();
275        if exp.scale() == 0 {
276            if exp.mid() != 0 || exp.hi() != 0 {
277                // Exponent way too big
278                return None;
279            }
280
281            return if exp.is_sign_negative() {
282                self.checked_powi(-(exp.lo() as i64))
283            } else {
284                self.checked_powu(exp.lo() as u64)
285            };
286        }
287
288        // We do some approximations since we've got a decimal exponent.
289        // For positive bases: a^b = exp(b*ln(a))
290        let negative = self.is_sign_negative();
291        let e = self.abs().ln().checked_mul(exp)?;
292        let mut result = e.checked_exp()?;
293        result.set_sign_negative(negative);
294        Some(result)
295    }
296
297    fn sqrt(&self) -> Option<Decimal> {
298        if self.is_sign_negative() {
299            return None;
300        }
301
302        if self.is_zero() {
303            return Some(Decimal::ZERO);
304        }
305
306        // Start with an arbitrary number as the first guess
307        let mut result = self / Decimal::TWO;
308        // Too small to represent, so we start with self
309        // Future iterations could actually avoid using a decimal altogether and use a buffered
310        // vector, only combining back into a decimal on return
311        if result.is_zero() {
312            result = *self;
313        }
314        let mut last = result + Decimal::ONE;
315
316        // Keep going while the difference is larger than the tolerance
317        let mut circuit_breaker = 0;
318        while last != result {
319            circuit_breaker += 1;
320            assert!(circuit_breaker < 1000, "geo mean circuit breaker");
321
322            last = result;
323            result = (result + self / result) / Decimal::TWO;
324        }
325
326        Some(result)
327    }
328
329    #[cfg(feature = "maths-nopanic")]
330    fn ln(&self) -> Decimal {
331        match self.checked_ln() {
332            Some(result) => result,
333            None => Decimal::ZERO,
334        }
335    }
336
337    #[cfg(not(feature = "maths-nopanic"))]
338    fn ln(&self) -> Decimal {
339        match self.checked_ln() {
340            Some(result) => result,
341            None => {
342                if self.is_sign_negative() {
343                    panic!("Unable to calculate ln for negative numbers")
344                } else if self.is_zero() {
345                    panic!("Unable to calculate ln for zero")
346                } else {
347                    panic!("Calculation of ln failed for unknown reasons")
348                }
349            }
350        }
351    }
352
353    fn checked_ln(&self) -> Option<Decimal> {
354        crate::ops::wide::ln_wide(self)
355    }
356
357    #[cfg(feature = "maths-nopanic")]
358    fn log10(&self) -> Decimal {
359        match self.checked_log10() {
360            Some(result) => result,
361            None => Decimal::ZERO,
362        }
363    }
364
365    #[cfg(not(feature = "maths-nopanic"))]
366    fn log10(&self) -> Decimal {
367        match self.checked_log10() {
368            Some(result) => result,
369            None => {
370                if self.is_sign_negative() {
371                    panic!("Unable to calculate log10 for negative numbers")
372                } else if self.is_zero() {
373                    panic!("Unable to calculate log10 for zero")
374                } else {
375                    panic!("Calculation of log10 failed for unknown reasons")
376                }
377            }
378        }
379    }
380
381    fn checked_log10(&self) -> Option<Decimal> {
382        use crate::ops::array::{div_by_u32, is_all_zero};
383        // Early exits
384        if self.is_sign_negative() || self.is_zero() {
385            return None;
386        }
387        if self.is_one() {
388            return Some(Decimal::ZERO);
389        }
390
391        // This uses a very basic method for calculating log10. We know the following is true:
392        //   log10(n) = ln(n) / ln(10)
393        // From this we can perform some small optimizations:
394        //  1. ln(10) is a constant
395        //  2. Multiplication is faster than division, so we can pre-calculate the constant 1/ln(10)
396        // This allows us to then simplify log10(n) to:
397        //   log10(n) = C * ln(n)
398
399        // Before doing all of this however, we see if there are simple calculations to be made.
400        let scale = self.scale();
401        let mut working = self.mantissa_array3();
402
403        // Check for scales less than 1 as an early exit
404        if scale > 0 && working[2] == 0 && working[1] == 0 && working[0] == 1 {
405            return Some(Decimal::from_parts(scale, 0, 0, true, 0));
406        }
407
408        // Loop for detecting bordering base 10 values
409        let mut result = 0;
410        let mut base10 = true;
411        while !is_all_zero(&working) {
412            let remainder = div_by_u32(&mut working, 10u32);
413            if remainder != 0 {
414                base10 = false;
415                break;
416            }
417            result += 1;
418            if working[2] == 0 && working[1] == 0 && working[0] == 1 {
419                break;
420            }
421        }
422        if base10 {
423            return Some((result - scale as i32).into());
424        }
425
426        self.checked_ln().map(|result| LN10_INVERSE * result)
427    }
428
429    fn erf(&self) -> Decimal {
430        if self.is_sign_positive() {
431            let one = &Decimal::ONE;
432
433            let xa1 = self * Decimal::from_parts(705230784, 0, 0, false, 10);
434            let xa2 = self.powi(2) * Decimal::from_parts(422820123, 0, 0, false, 10);
435            let xa3 = self.powi(3) * Decimal::from_parts(92705272, 0, 0, false, 10);
436            let xa4 = self.powi(4) * Decimal::from_parts(1520143, 0, 0, false, 10);
437            let xa5 = self.powi(5) * Decimal::from_parts(2765672, 0, 0, false, 10);
438            let xa6 = self.powi(6) * Decimal::from_parts(430638, 0, 0, false, 10);
439
440            let sum = one + xa1 + xa2 + xa3 + xa4 + xa5 + xa6;
441            one - (one / sum.powi(16))
442        } else {
443            -self.abs().erf()
444        }
445    }
446
447    fn norm_cdf(&self) -> Decimal {
448        (Decimal::ONE + (self / Decimal::from_parts(2318911239, 3292722, 0, false, 16)).erf()) / Decimal::TWO
449    }
450
451    fn norm_pdf(&self) -> Decimal {
452        match self.checked_norm_pdf() {
453            Some(d) => d,
454            None => panic!("Norm Pdf overflowed"),
455        }
456    }
457
458    fn checked_norm_pdf(&self) -> Option<Decimal> {
459        let sqrt2pi = Decimal::from_parts_raw(2133383024, 2079885984, 1358845910, 1835008);
460        let factor = -self.checked_powi(2)?;
461        let factor = factor.checked_div(Decimal::TWO)?;
462        factor.checked_exp()?.checked_div(sqrt2pi)
463    }
464
465    fn sin(&self) -> Decimal {
466        match self.checked_sin() {
467            Some(x) => x,
468            None => panic!("Sin overflowed"),
469        }
470    }
471
472    fn checked_sin(&self) -> Option<Decimal> {
473        crate::ops::wide::sin_wide(self)
474    }
475
476    fn cos(&self) -> Decimal {
477        match self.checked_cos() {
478            Some(x) => x,
479            None => panic!("Cos overflowed"),
480        }
481    }
482
483    fn checked_cos(&self) -> Option<Decimal> {
484        crate::ops::wide::cos_wide(self)
485    }
486
487    fn tan(&self) -> Decimal {
488        match self.checked_tan() {
489            Some(x) => x,
490            None => panic!("Tan overflowed"),
491        }
492    }
493
494    fn checked_tan(&self) -> Option<Decimal> {
495        if self.is_zero() {
496            return Some(Decimal::ZERO);
497        }
498        if self.is_sign_negative() {
499            // -Tan(-x)
500            return (-self).checked_tan().map(|x| -x);
501        }
502        if self >= &Decimal::TWO_PI {
503            // Reduce large numbers early - we can do this using rem to constrain to a range
504            let adjusted = self.checked_rem(Decimal::TWO_PI)?;
505            return adjusted.checked_tan();
506        }
507        // Reduce to 0 <= x <= PI
508        if self >= &Decimal::PI {
509            // Tan(x-π)
510            return (self - Decimal::PI).checked_tan();
511        }
512        // Reduce to 0 <= x <= PI/2
513        if self > &Decimal::HALF_PI {
514            // We can use the symmetrical function inside the first quadrant
515            // e.g. tan(x) = -tan((PI/2 - x) + PI/2)
516            return ((Decimal::HALF_PI - self) + Decimal::HALF_PI).checked_tan().map(|x| -x);
517        }
518
519        // It has now been reduced to 0 <= x <= PI/2. If it is >= PI/4 we can make it even smaller
520        // by calculating tan(PI/2 - x) and taking the reciprocal
521        if self > &Decimal::QUARTER_PI {
522            return match (Decimal::HALF_PI - self).checked_tan() {
523                Some(x) => Decimal::ONE.checked_div(x),
524                None => None,
525            };
526        }
527
528        // Due the way that tan(x) sharply tends towards infinity, we try to optimize
529        // the resulting accuracy by using Trigonometric identity when > PI/8. We do this by
530        // replacing the angle with one that is half as big.
531        if self > &EIGHTH_PI {
532            // Work out tan(x/2)
533            let tan_half = (self / Decimal::TWO).checked_tan()?;
534            // Work out the dividend i.e. 2tan(x/2)
535            let dividend = Decimal::TWO.checked_mul(tan_half)?;
536
537            // Work out the divisor i.e. 1 - tan^2(x/2)
538            let squared = tan_half.checked_mul(tan_half)?;
539            let divisor = Decimal::ONE - squared;
540            // Treat this as infinity
541            if divisor.is_zero() {
542                return None;
543            }
544            return dividend.checked_div(divisor);
545        }
546
547        // Do a polynomial approximation based upon the Maclaurin series.
548        // This can be simplified to something like:
549        //
550        // ∑(n=1,3,5,7,9)(f(n)(0)/n!)x^n
551        //
552        // First few expansions (which we leverage):
553        // (f'(0)/1!)x^1 + (f'''(0)/3!)x^3 + (f'''''(0)/5!)x^5 + (f'''''''/7!)x^7
554        //
555        // x + (1/3)x^3 + (2/15)x^5 + (17/315)x^7 + (62/2835)x^9 + (1382/155925)x^11
556        //
557        // (Generated by https://www.wolframalpha.com/widgets/view.jsp?id=fe1ad8d4f5dbb3cb866d0c89beb527a6)
558        // The more terms, the better the accuracy. This generates accuracy within approx 10^-8 for angles
559        // less than PI/8.
560        const SERIES: [(Decimal, u64); 6] = [
561            // 1 / 3
562            (Decimal::from_parts_raw(89478485, 347537611, 180700362, 1835008), 3),
563            // 2 / 15
564            (Decimal::from_parts_raw(894784853, 3574988881, 72280144, 1835008), 5),
565            // 17 / 315
566            (Decimal::from_parts_raw(905437054, 3907911371, 2925624, 1769472), 7),
567            // 62 / 2835
568            (Decimal::from_parts_raw(3191872741, 2108928381, 11855473, 1835008), 9),
569            // 1382 / 155925
570            (Decimal::from_parts_raw(3482645539, 2612995122, 4804769, 1835008), 11),
571            // 21844 / 6081075
572            (Decimal::from_parts_raw(4189029078, 2192791200, 1947296, 1835008), 13),
573        ];
574        let mut result = *self;
575        for (fraction, pow) in SERIES {
576            result += fraction * self.powu(pow);
577        }
578        Some(result)
579    }
580}
581
582impl Pow<Decimal> for Decimal {
583    type Output = Decimal;
584
585    fn pow(self, rhs: Decimal) -> Self::Output {
586        MathematicalOps::powd(&self, rhs)
587    }
588}
589
590impl Pow<u64> for Decimal {
591    type Output = Decimal;
592
593    fn pow(self, rhs: u64) -> Self::Output {
594        MathematicalOps::powu(&self, rhs)
595    }
596}
597
598impl Pow<i64> for Decimal {
599    type Output = Decimal;
600
601    fn pow(self, rhs: i64) -> Self::Output {
602        MathematicalOps::powi(&self, rhs)
603    }
604}
605
606impl Pow<f64> for Decimal {
607    type Output = Decimal;
608
609    fn pow(self, rhs: f64) -> Self::Output {
610        MathematicalOps::powf(&self, rhs)
611    }
612}
613
614#[cfg(test)]
615mod test {
616    use super::*;
617
618    #[cfg(not(feature = "std"))]
619    use alloc::string::ToString;
620
621    #[test]
622    fn test_factorials() {
623        assert_eq!("1", FACTORIAL[0].to_string(), "0!");
624        assert_eq!("1", FACTORIAL[1].to_string(), "1!");
625        assert_eq!("2", FACTORIAL[2].to_string(), "2!");
626        assert_eq!("6", FACTORIAL[3].to_string(), "3!");
627        assert_eq!("24", FACTORIAL[4].to_string(), "4!");
628        assert_eq!("120", FACTORIAL[5].to_string(), "5!");
629        assert_eq!("720", FACTORIAL[6].to_string(), "6!");
630        assert_eq!("5040", FACTORIAL[7].to_string(), "7!");
631        assert_eq!("40320", FACTORIAL[8].to_string(), "8!");
632        assert_eq!("362880", FACTORIAL[9].to_string(), "9!");
633        assert_eq!("3628800", FACTORIAL[10].to_string(), "10!");
634        assert_eq!("39916800", FACTORIAL[11].to_string(), "11!");
635        assert_eq!("479001600", FACTORIAL[12].to_string(), "12!");
636        assert_eq!("6227020800", FACTORIAL[13].to_string(), "13!");
637        assert_eq!("87178291200", FACTORIAL[14].to_string(), "14!");
638        assert_eq!("1307674368000", FACTORIAL[15].to_string(), "15!");
639        assert_eq!("20922789888000", FACTORIAL[16].to_string(), "16!");
640        assert_eq!("355687428096000", FACTORIAL[17].to_string(), "17!");
641        assert_eq!("6402373705728000", FACTORIAL[18].to_string(), "18!");
642        assert_eq!("121645100408832000", FACTORIAL[19].to_string(), "19!");
643        assert_eq!("2432902008176640000", FACTORIAL[20].to_string(), "20!");
644        assert_eq!("51090942171709440000", FACTORIAL[21].to_string(), "21!");
645        assert_eq!("1124000727777607680000", FACTORIAL[22].to_string(), "22!");
646        assert_eq!("25852016738884976640000", FACTORIAL[23].to_string(), "23!");
647        assert_eq!("620448401733239439360000", FACTORIAL[24].to_string(), "24!");
648        assert_eq!("15511210043330985984000000", FACTORIAL[25].to_string(), "25!");
649        assert_eq!("403291461126605635584000000", FACTORIAL[26].to_string(), "26!");
650        assert_eq!("10888869450418352160768000000", FACTORIAL[27].to_string(), "27!");
651    }
652}