Skip to main content

factorion_lib/
calculation_tasks.rs

1//! This module handles the calculation of pending calculation tasks
2
3use factorion_math::rug::ops::AddFrom;
4#[cfg(any(feature = "serde", test))]
5use serde::{Deserialize, Serialize};
6
7use crate::calculation_results::Number;
8
9use crate::Consts;
10use crate::{
11    calculation_results::{Calculation, CalculationResult},
12    math,
13};
14
15use crate::rug::{Float, ops::Pow};
16
17pub mod recommended {
18    use factorion_math::rug::Complete;
19    use factorion_math::rug::integer::IntegerExt64;
20
21    use crate::rug::Integer;
22    // Limit for exact calculation, set to limit calculation time
23    pub static UPPER_CALCULATION_LIMIT: fn() -> Integer = || 1_000_000.into();
24    // Limit for approximation, set to ensure enough accuracy (5 decimals)
25    pub static UPPER_APPROXIMATION_LIMIT: fn() -> Integer =
26        || Integer::u64_pow_u64(10, 300).complete();
27    // Limit for exact subfactorial calculation, set to limit calculation time
28    pub static UPPER_SUBFACTORIAL_LIMIT: fn() -> Integer = || 100_000.into();
29    // Limit for exact termial calculation, set to limit calculation time (absurdly high)
30    pub static UPPER_TERMIAL_LIMIT: fn() -> Integer = || Integer::u64_pow_u64(10, 10000).complete();
31    // Limit for approximation, set to ensure enough accuracy (5 decimals)
32    // Based on max float. (bits)
33    pub static UPPER_TERMIAL_APPROXIMATION_LIMIT: u32 = 1073741822;
34}
35
36/// Representation of the calculation to be done
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
38#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
39pub struct CalculationJob {
40    pub base: CalculationBase,
41    /// Type of the calculation
42    pub level: i32,
43    /// Number of negations encountered
44    pub negative: u32,
45}
46/// The basis of a calculation, whether [Number] or [CalculationJob].
47#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
48#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
49pub enum CalculationBase {
50    Num(Number),
51    Calc(Box<CalculationJob>),
52}
53
54impl CalculationJob {
55    /// Execute the calculation. \
56    /// If include_steps is enabled, will return all intermediate results.
57    pub fn execute(self, include_steps: bool, consts: &Consts) -> Vec<Option<Calculation>> {
58        let CalculationJob {
59            mut base,
60            mut level,
61            mut negative,
62        } = self;
63        let size = {
64            let mut n = 1;
65            let mut b = &base;
66            while let CalculationBase::Calc(inner) = b {
67                n += 1;
68                b = &inner.base;
69            }
70            n
71        };
72        // TODO: Maybe ignore include steps if size is too big (we can't respond properly anyway)
73        let mut steps = Vec::with_capacity(size);
74        let mut calcs = loop {
75            match base {
76                CalculationBase::Num(num) => {
77                    break vec![
78                        Self::calculate_appropriate_factorial(num.clone(), level, negative, consts)
79                            .map(|res| Calculation {
80                                value: num,
81                                steps: vec![(level, negative % 2 == 1)],
82                                result: res,
83                            }),
84                    ];
85                }
86                CalculationBase::Calc(calc) => {
87                    steps.push((level, negative));
88                    CalculationJob {
89                        base,
90                        level,
91                        negative,
92                    } = *calc;
93                }
94            }
95        };
96        for (i, (level, negative)) in steps.into_iter().rev().enumerate() {
97            let calc = if include_steps && i < 30 {
98                calcs.last().cloned()
99            } else {
100                calcs.pop()
101            };
102            match calc {
103                Some(Some(Calculation {
104                    result: res,
105                    mut steps,
106                    value: number,
107                })) => {
108                    let factorial = Self::calculate_appropriate_factorial(
109                        res, level, negative, consts,
110                    )
111                    .map(|res| {
112                        steps.push((level, negative % 2 == 1));
113                        Calculation {
114                            value: number,
115                            steps,
116                            result: res,
117                        }
118                    });
119                    calcs.push(factorial);
120                }
121                _ => return calcs,
122            };
123        }
124        calcs
125    }
126    fn calculate_appropriate_factorial(
127        num: Number,
128        level: i32,
129        negative: u32,
130        consts: &Consts,
131    ) -> Option<CalculationResult> {
132        let prec = consts.float_precision;
133        let calc_num = match num {
134            CalculationResult::ComplexInfinity => return Some(CalculationResult::ComplexInfinity),
135            Number::Float(num) | CalculationResult::Approximate(num, _)
136                if !num.as_float().is_finite() =>
137            {
138                return Some(CalculationResult::ComplexInfinity);
139            }
140            CalculationResult::Approximate(base, exponent) => {
141                if exponent <= consts.integer_construction_limit {
142                    let x: Float = base.as_float() * Float::with_val(prec, 10).pow(&exponent);
143                    x.to_integer().unwrap()
144                } else {
145                    return Some(if base.as_float() < &0.0 {
146                        CalculationResult::ComplexInfinity
147                    } else if level < 0 {
148                        let termial = math::approximate_approx_termial(
149                            (Float::from(base), exponent),
150                            -level as u32,
151                        );
152                        if termial.0 == 1 {
153                            CalculationResult::ApproximateDigits(false, termial.1)
154                        } else {
155                            CalculationResult::Approximate(termial.0.into(), termial.1)
156                        }
157                    } else {
158                        let mut exponent = exponent;
159                        exponent.add_from(math::length(&exponent, prec));
160                        CalculationResult::ApproximateDigitsTower(false, false, 1.into(), exponent)
161                    });
162                }
163            }
164            CalculationResult::ApproximateDigits(was_neg, digits) => {
165                if digits <= consts.integer_construction_limit {
166                    let x: Float = Float::with_val(prec, 10).pow(digits.clone() - 1);
167                    x.to_integer().unwrap()
168                } else {
169                    return Some(if digits.is_negative() {
170                        CalculationResult::Float(Float::new(prec).into())
171                    } else if was_neg {
172                        CalculationResult::ComplexInfinity
173                    } else if level < 0 {
174                        let mut one = Float::with_val(consts.float_precision, 1);
175                        if was_neg {
176                            one *= -1;
177                        }
178                        let termial =
179                            math::approximate_approx_termial((one, digits), -level as u32);
180                        if termial.0 == 1 {
181                            CalculationResult::ApproximateDigits(false, termial.1)
182                        } else {
183                            CalculationResult::Approximate(termial.0.into(), termial.1)
184                        }
185                    } else {
186                        let mut digits = digits;
187                        digits.add_from(math::length(&digits, prec));
188                        CalculationResult::ApproximateDigitsTower(false, false, 1.into(), digits)
189                    });
190                }
191            }
192            CalculationResult::ApproximateDigitsTower(was_neg, neg, depth, exponent) => {
193                return Some(if neg {
194                    CalculationResult::Float(Float::new(prec).into())
195                } else if was_neg {
196                    CalculationResult::ComplexInfinity
197                } else if level < 0 {
198                    CalculationResult::ApproximateDigitsTower(false, false, depth, exponent)
199                } else {
200                    CalculationResult::ApproximateDigitsTower(false, false, depth + 1, exponent)
201                });
202            }
203            Number::Float(num) => match level {
204                ..-1 => {
205                    // We don't support multitermials of decimals
206                    return None;
207                }
208                -1 => {
209                    let res: Float = math::fractional_termial(num.as_float().clone())
210                        * if !negative.is_multiple_of(2) { -1 } else { 1 };
211                    if res.is_finite() {
212                        return Some(CalculationResult::Float(res.into()));
213                    } else {
214                        num.as_float().to_integer()?
215                    }
216                }
217                0 => {
218                    // We don't support subfactorials of deciamals
219                    return None;
220                }
221                1 => {
222                    let res: Float = math::fractional_factorial(num.as_float().clone())
223                        * if !negative.is_multiple_of(2) { -1 } else { 1 };
224                    if res.is_finite() {
225                        return Some(CalculationResult::Float(res.into()));
226                    } else {
227                        num.as_float().to_integer()?
228                    }
229                }
230                2.. => {
231                    let res: Float =
232                        math::fractional_multifactorial(num.as_float().clone(), level as u32)
233                            * if !negative.is_multiple_of(2) { -1 } else { 1 };
234                    if res.is_finite() {
235                        return Some(CalculationResult::Float(res.into()));
236                    } else {
237                        num.as_float().to_integer()?
238                    }
239                }
240            },
241            Number::Exact(num) if num.significant_bits() >= math::rug::float::exp_max() as u32 => {
242                let sig_bits = num.significant_bits();
243                return Self::calculate_appropriate_factorial(
244                    CalculationResult::Approximate(
245                        (Float::with_val(consts.float_precision, num / consts.float_precision)
246                            / (sig_bits - consts.float_precision))
247                            .into(),
248                        sig_bits.into(),
249                    ),
250                    level,
251                    negative,
252                    consts,
253                );
254            }
255            Number::Exact(num) => num,
256        };
257        if level > 0 {
258            Some(if calc_num < 0 && level == 1 {
259                CalculationResult::ComplexInfinity
260            } else if calc_num < 0 {
261                let factor = math::negative_multifacorial_factor(calc_num.clone(), level);
262                match (factor, -level - 1 > calc_num) {
263                    (Some(factor), true) => {
264                        let mut res = Self::calculate_appropriate_factorial(
265                            Number::Exact(-calc_num.clone() - level),
266                            level,
267                            negative,
268                            consts,
269                        )?;
270                        res = match res {
271                            CalculationResult::Exact(n) => {
272                                let n = Float::with_val(prec, n);
273                                CalculationResult::Float((factor / n).into())
274                            }
275                            CalculationResult::Approximate(b, e) => {
276                                let (b, e) =
277                                    math::adjust_approximate((factor / Float::from(b), -e));
278                                CalculationResult::Approximate(b.into(), e)
279                            }
280                            CalculationResult::ApproximateDigits(wn, n) => {
281                                CalculationResult::ApproximateDigits(wn, -n)
282                            }
283                            CalculationResult::ApproximateDigitsTower(
284                                wn,
285                                negative,
286                                depth,
287                                base,
288                            ) => CalculationResult::ApproximateDigitsTower(
289                                wn, !negative, depth, base,
290                            ),
291                            CalculationResult::ComplexInfinity => {
292                                CalculationResult::Exact(0.into())
293                            }
294                            CalculationResult::Float(f) => {
295                                CalculationResult::Float((factor / Float::from(f)).into())
296                            }
297                        };
298
299                        res
300                    }
301                    (factor, _) => factor
302                        .map(CalculationResult::Exact)
303                        .unwrap_or(CalculationResult::ComplexInfinity),
304                }
305                // Check if we can approximate the number of digits
306            } else if calc_num > consts.upper_approximation_limit {
307                let factorial =
308                    math::approximate_multifactorial_digits(calc_num.clone(), level as u32, prec);
309                CalculationResult::ApproximateDigits(!negative.is_multiple_of(2), factorial)
310            // Check if the number is within a reasonable range to compute
311            } else if calc_num > consts.upper_calculation_limit {
312                let factorial = if level == 0 {
313                    math::approximate_factorial(calc_num.clone(), prec)
314                } else {
315                    math::approximate_multifactorial(calc_num.clone(), level as u32, prec)
316                };
317                CalculationResult::Approximate(
318                    ((factorial.0 * if !negative.is_multiple_of(2) { -1 } else { 1 }) as Float)
319                        .into(),
320                    factorial.1,
321                )
322            } else {
323                let calc_num = calc_num
324                    .to_u64()
325                    .unwrap_or_else(|| panic!("Failed to convert BigInt to u64: {calc_num}"));
326                let factorial = math::factorial(calc_num, level as u32)
327                    * if !negative.is_multiple_of(2) { -1 } else { 1 };
328                CalculationResult::Exact(factorial)
329            })
330        } else if level == 0 {
331            Some(if calc_num < 0 {
332                CalculationResult::ComplexInfinity
333            } else if calc_num > consts.upper_approximation_limit {
334                let factorial = math::approximate_multifactorial_digits(calc_num.clone(), 1, prec);
335                CalculationResult::ApproximateDigits(!negative.is_multiple_of(2), factorial)
336            } else if calc_num > consts.upper_subfactorial_limit {
337                let factorial = math::approximate_subfactorial(calc_num.clone(), prec);
338                CalculationResult::Approximate(
339                    ((factorial.0 * if !negative.is_multiple_of(2) { -1 } else { 1 }) as Float)
340                        .into(),
341                    factorial.1,
342                )
343            } else {
344                let calc_num = calc_num
345                    .to_u64()
346                    .unwrap_or_else(|| panic!("Failed to convert BigInt to u64: {calc_num}"));
347                let factorial =
348                    math::subfactorial(calc_num) * if !negative.is_multiple_of(2) { -1 } else { 1 };
349                CalculationResult::Exact(factorial)
350            })
351        } else if level < 0 {
352            Some(
353                if calc_num.significant_bits() > consts.upper_termial_approximation_limit {
354                    let termial =
355                        math::approximate_termial_digits(calc_num, level.unsigned_abs(), prec);
356                    CalculationResult::ApproximateDigits(!negative.is_multiple_of(2), termial)
357                } else if *calc_num.as_abs() > consts.upper_termial_limit {
358                    let termial = math::approximate_termial(calc_num, level.unsigned_abs(), prec);
359                    CalculationResult::Approximate(
360                        ((termial.0 * if !negative.is_multiple_of(2) { -1 } else { 1 }) as Float)
361                            .into(),
362                        termial.1,
363                    )
364                } else {
365                    let termial = if level < -1 {
366                        math::multitermial(calc_num, level.unsigned_abs())
367                    } else {
368                        math::termial(calc_num)
369                    };
370                    let termial = termial * if !negative.is_multiple_of(2) { -1 } else { 1 };
371                    CalculationResult::Exact(termial)
372                },
373            )
374        } else {
375            unreachable!()
376        }
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use factorion_math::recommended::FLOAT_PRECISION;
384
385    #[test]
386    fn test_unsupported_calcs() {
387        let consts = Consts::default();
388        // Subfactorial
389        let job = CalculationJob {
390            base: CalculationBase::Num(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into())),
391            level: 0,
392            negative: 0,
393        };
394        assert_eq!(job.execute(false, &consts), vec![None]);
395        // Multitermial
396        let job = CalculationJob {
397            base: CalculationBase::Num(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into())),
398            level: -2,
399            negative: 0,
400        };
401        assert_eq!(job.execute(false, &consts), vec![None]);
402        let job = CalculationJob {
403            base: CalculationBase::Num(Number::Float(Float::with_val(FLOAT_PRECISION, 1.5).into())),
404            level: -51,
405            negative: 0,
406        };
407        assert_eq!(job.execute(false, &consts), vec![None]);
408    }
409}