use std::f64;
use std::sync::{Once, ONCE_INIT};
use error::StatsError;
use function::gamma;
pub const MAX_ARG: u64 = 170;
pub fn factorial(x: u64) -> f64 {
if x > MAX_ARG {
f64::INFINITY
} else {
get_fcache()[x as usize]
}
}
pub fn ln_factorial(x: u64) -> f64 {
if x <= 1 {
0.0
} else if x > MAX_ARG {
gamma::ln_gamma(x as f64 + 1.0)
} else {
get_fcache()[x as usize].ln()
}
}
pub fn binomial(n: u64, k: u64) -> f64 {
if k > n {
0.0
} else {
(0.5 + (ln_factorial(n) - ln_factorial(k) - ln_factorial(n - k)).exp()).floor()
}
}
pub fn ln_binomial(n: u64, k: u64) -> f64 {
if k > n {
f64::NEG_INFINITY
} else {
ln_factorial(n) - ln_factorial(k) - ln_factorial(n - k)
}
}
pub fn multinomial(n: u64, ni: &[u64]) -> f64 {
let (sum, ret) = ni.iter().fold((0, ln_factorial(n)),
|acc, &x| (acc.0 + x, acc.1 - ln_factorial(x)));
assert!(sum == n,
format!("{}", StatsError::ContainerExpectedSumVar("ni", "n")));
(0.5 + ret.exp()).floor()
}
const CACHE_SIZE: usize = 171;
static mut FCACHE: &'static mut [f64; CACHE_SIZE] = &mut [1.0; CACHE_SIZE];
static START: Once = ONCE_INIT;
fn get_fcache() -> &'static [f64; CACHE_SIZE] {
unsafe {
START.call_once(|| {
(1..CACHE_SIZE).fold(FCACHE[0], |acc, i| {
let fac = acc * i as f64;
FCACHE[i] = fac;
fac
});
});
FCACHE
}
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[cfg(test)]
mod test {
use std::{f64, u64};
#[test]
fn test_factorial_and_ln_factorial() {
let mut factorial = 1.0;
for i in 1..171 {
factorial *= i as f64;
assert_eq!(super::factorial(i), factorial);
assert_eq!(super::ln_factorial(i), factorial.ln());
}
}
#[test]
fn test_factorial_overflow() {
assert_eq!(super::factorial(172), f64::INFINITY);
assert_eq!(super::factorial(u64::MAX), f64::INFINITY);
}
#[test]
fn test_ln_factorial_does_not_overflow() {
assert_eq!(super::ln_factorial(1 << 10), 6078.2118847500501140);
assert_almost_eq!(super::ln_factorial(1 << 12), 29978.648060844048236, 1e-11);
assert_eq!(super::ln_factorial(1 << 15), 307933.81973375485425);
assert_eq!(super::ln_factorial(1 << 17), 1413421.9939462073242);
}
#[test]
fn test_binomial() {
assert_eq!(super::binomial(1, 1), 1.0);
assert_eq!(super::binomial(5, 2), 10.0);
assert_eq!(super::binomial(7, 3), 35.0);
assert_eq!(super::binomial(1, 0), 1.0);
assert_eq!(super::binomial(0, 1), 0.0);
assert_eq!(super::binomial(5, 7), 0.0);
}
#[test]
fn test_ln_binomial() {
assert_eq!(super::ln_binomial(1, 1), 1f64.ln());
assert_almost_eq!(super::ln_binomial(5, 2), 10f64.ln(), 1e-14);
assert_almost_eq!(super::ln_binomial(7, 3), 35f64.ln(), 1e-14);
assert_eq!(super::ln_binomial(1, 0), 1f64.ln());
assert_eq!(super::ln_binomial(0, 1), 0f64.ln());
assert_eq!(super::ln_binomial(5, 7), 0f64.ln());
}
#[test]
fn test_multinomial() {
assert_eq!(1.0, super::multinomial(1, &[1, 0]));
assert_eq!(10.0, super::multinomial(5, &[3, 2]));
assert_eq!(10.0, super::multinomial(5, &[2, 3]));
assert_eq!(35.0, super::multinomial(7, &[3, 4]));
}
}