Compare commits
1 Commits
dani/widge
...
fact_gamma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a00ecfdade |
@@ -8,8 +8,74 @@ use crate::single_number_fn;
|
|||||||
use crate::{
|
use crate::{
|
||||||
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
||||||
};
|
};
|
||||||
|
use statrs::function::gamma::ln_gamma;
|
||||||
|
use std::f64::consts::LN_2;
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
|
const FACT_TABLE: [f64; 26] = [
|
||||||
|
1.0, // 0!
|
||||||
|
1.0, // 1!
|
||||||
|
2.0, // 2!
|
||||||
|
6.0, // 3!
|
||||||
|
24.0, // 4!
|
||||||
|
120.0, // 5!
|
||||||
|
720.0, // 6!
|
||||||
|
5040.0, // 7!
|
||||||
|
40320.0, // 8!
|
||||||
|
362880.0, // 9!
|
||||||
|
3628800.0, // 10!
|
||||||
|
39916800.0, // 11!
|
||||||
|
479001600.0, // 12!
|
||||||
|
6227020800.0, // 13!
|
||||||
|
87178291200.0, // 14!
|
||||||
|
1307674368000.0, // 15!
|
||||||
|
20922789888000.0, // 16!
|
||||||
|
355687428096000.0, // 17!
|
||||||
|
6402373705728000.0, // 18!
|
||||||
|
121645100408832000.0, // 19!
|
||||||
|
2432902008176640000.0, // 20!
|
||||||
|
51090942171709440000.0, // 21!
|
||||||
|
1124000727777607680000.0, // 22!
|
||||||
|
25852016738884976640000.0, // 23!
|
||||||
|
620448401733239439360000.0, // 24!
|
||||||
|
15511210043330985984000000.0, // 25!
|
||||||
|
];
|
||||||
|
|
||||||
|
const FACTDOUBLE_TABLE: [f64; 32] = [
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
3.0,
|
||||||
|
8.0,
|
||||||
|
15.0,
|
||||||
|
48.0,
|
||||||
|
105.0,
|
||||||
|
384.0,
|
||||||
|
945.0,
|
||||||
|
3840.0,
|
||||||
|
10395.0,
|
||||||
|
46080.0,
|
||||||
|
135135.0,
|
||||||
|
645120.0,
|
||||||
|
2027025.0,
|
||||||
|
10321920.0,
|
||||||
|
34459425.0,
|
||||||
|
185794560.0,
|
||||||
|
654729075.0,
|
||||||
|
3715891200.0,
|
||||||
|
13749310575.0,
|
||||||
|
81749606400.0,
|
||||||
|
316234143225.0,
|
||||||
|
1961990553600.0,
|
||||||
|
7905853580625.0,
|
||||||
|
51011754393600.0,
|
||||||
|
213458046676875.0,
|
||||||
|
1428329123020800.0,
|
||||||
|
6190283353629370.0,
|
||||||
|
42849873690624000.0,
|
||||||
|
191898783962511000.0,
|
||||||
|
];
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn random() -> f64 {
|
pub fn random() -> f64 {
|
||||||
rand::random()
|
rand::random()
|
||||||
@@ -1319,35 +1385,78 @@ impl Model {
|
|||||||
single_number_fn!(fn_sec, |f| Ok(1.0 / f64::cos(f)));
|
single_number_fn!(fn_sec, |f| Ok(1.0 / f64::cos(f)));
|
||||||
single_number_fn!(fn_sech, |f| Ok(1.0 / f64::cosh(f)));
|
single_number_fn!(fn_sech, |f| Ok(1.0 / f64::cosh(f)));
|
||||||
single_number_fn!(fn_exp, |f: f64| Ok(f64::exp(f)));
|
single_number_fn!(fn_exp, |f: f64| Ok(f64::exp(f)));
|
||||||
|
|
||||||
single_number_fn!(fn_fact, |x: f64| {
|
single_number_fn!(fn_fact, |x: f64| {
|
||||||
let x = x.floor();
|
let x = x.floor();
|
||||||
|
|
||||||
if x < 0.0 {
|
if x < 0.0 {
|
||||||
return Err(Error::NUM);
|
return Err(Error::NUM);
|
||||||
}
|
}
|
||||||
let mut acc = 1.0;
|
|
||||||
let mut k = 2.0;
|
if x == 0.0 {
|
||||||
while k <= x {
|
|
||||||
acc *= k;
|
|
||||||
k += 1.0;
|
|
||||||
}
|
|
||||||
Ok(acc)
|
|
||||||
});
|
|
||||||
single_number_fn!(fn_factdouble, |x: f64| {
|
|
||||||
let x = x.floor();
|
|
||||||
if x < -1.0 {
|
|
||||||
return Err(Error::NUM);
|
|
||||||
}
|
|
||||||
if x < 0.0 {
|
|
||||||
return Ok(1.0);
|
return Ok(1.0);
|
||||||
}
|
}
|
||||||
let mut acc = 1.0;
|
if x < FACT_TABLE.len() as f64 {
|
||||||
let mut k = if x % 2.0 == 0.0 { 2.0 } else { 1.0 };
|
return Ok(FACT_TABLE[x as usize]);
|
||||||
while k <= x {
|
|
||||||
acc *= k;
|
|
||||||
k += 2.0;
|
|
||||||
}
|
}
|
||||||
Ok(acc)
|
|
||||||
|
// Use ln Γ(x+1) to avoid overflow while deciding.
|
||||||
|
let ln_val = ln_gamma(x + 1.0);
|
||||||
|
|
||||||
|
// If gamma overflows or is invalid, map to NUM
|
||||||
|
if !ln_val.is_finite() || ln_val > f64::MAX.ln() {
|
||||||
|
return Err(Error::NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ln_val.exp())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
single_number_fn!(fn_factdouble, |x: f64| {
|
||||||
|
let x = x.floor();
|
||||||
|
if x <= -1.0 {
|
||||||
|
return Err(Error::NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if x <= 1.0 {
|
||||||
|
return Ok(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// From here x > 1 and integer
|
||||||
|
let n = x as i64;
|
||||||
|
|
||||||
|
if n < FACTDOUBLE_TABLE.len() as i64 {
|
||||||
|
return Ok(FACTDOUBLE_TABLE[n as usize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// n!! grows very fast, so we compute it using gamma in log-space:
|
||||||
|
//
|
||||||
|
// If n is even, n = 2k:
|
||||||
|
// n!! = 2^k * k!
|
||||||
|
// If n is odd, n = 2k + 1:
|
||||||
|
// n!! = (2k+1)! / (2^k * k!)
|
||||||
|
//
|
||||||
|
// and we use ln_gamma for factorials.
|
||||||
|
|
||||||
|
let ln_val = if n % 2 == 0 {
|
||||||
|
// even n = 2k
|
||||||
|
let k = (n / 2) as f64;
|
||||||
|
// ln(n!!) = k * ln(2) + ln(k!)
|
||||||
|
k * LN_2 + ln_gamma(k + 1.0)
|
||||||
|
} else {
|
||||||
|
// odd n = 2k + 1
|
||||||
|
let k = ((n - 1) / 2) as f64;
|
||||||
|
let nn = n as f64;
|
||||||
|
// ln(n!!) = ln((2k+1)!) - (k * ln(2) + ln(k!))
|
||||||
|
ln_gamma(nn + 1.0) - (k * LN_2 + ln_gamma(k + 1.0))
|
||||||
|
};
|
||||||
|
|
||||||
|
if !ln_val.is_finite() || ln_val > f64::MAX.ln() {
|
||||||
|
return Err(Error::NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ln_val.exp())
|
||||||
|
});
|
||||||
|
|
||||||
single_number_fn!(fn_sign, |f| {
|
single_number_fn!(fn_sign, |f| {
|
||||||
if f == 0.0 {
|
if f == 0.0 {
|
||||||
Ok(0.0)
|
Ok(0.0)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ mod test_fn_concatenate;
|
|||||||
mod test_fn_count;
|
mod test_fn_count;
|
||||||
mod test_fn_day;
|
mod test_fn_day;
|
||||||
mod test_fn_exact;
|
mod test_fn_exact;
|
||||||
|
mod test_fn_fact;
|
||||||
mod test_fn_financial;
|
mod test_fn_financial;
|
||||||
mod test_fn_formulatext;
|
mod test_fn_formulatext;
|
||||||
mod test_fn_if;
|
mod test_fn_if;
|
||||||
|
|||||||
29
base/src/test/test_fn_fact.rs
Normal file
29
base/src/test/test_fn_fact.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::test::util::new_empty_model;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn large_numbers() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model._set("A1", "=FACT(170)");
|
||||||
|
model._set("A2", "=FACTDOUBLE(36)");
|
||||||
|
|
||||||
|
model._set("B1", "=FACT(6)");
|
||||||
|
model._set("B2", "=FACTDOUBLE(6)");
|
||||||
|
|
||||||
|
model._set("C3", "=FACTDOUBLE(15)");
|
||||||
|
|
||||||
|
model._set("F3", "=FACT(-0.1)");
|
||||||
|
model._set("F4", "=FACT(0)");
|
||||||
|
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
assert_eq!(model._get_text("A1"), *"7.25742E+306");
|
||||||
|
assert_eq!(model._get_text("A2"), *"1.67834E+21");
|
||||||
|
assert_eq!(model._get_text("B1"), *"720");
|
||||||
|
assert_eq!(model._get_text("B2"), *"48");
|
||||||
|
assert_eq!(model._get_text("C3"), *"2027025");
|
||||||
|
|
||||||
|
assert_eq!(model._get_text("F3"), *"#NUM!");
|
||||||
|
assert_eq!(model._get_text("F4"), *"1");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user