use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
pub fn factorial(n: u64) -> BigInt {
if n < 2 {
return BigInt::one();
}
product_range(2, n)
}
fn product_range(lo: u64, hi: u64) -> BigInt {
const LEAF: u64 = 16;
if hi - lo < LEAF {
let mut acc = BigInt::from(lo);
for i in lo + 1..=hi {
acc *= i;
}
return acc;
}
let mid = lo + (hi - lo) / 2;
product_range(lo, mid) * product_range(mid + 1, hi)
}
pub fn binomial(n: impl Into<BigInt>, k: impl Into<BigInt>) -> BigInt {
let n: BigInt = n.into();
let k: BigInt = k.into();
if k.is_negative() {
return BigInt::zero();
}
if n.is_negative() {
let c = binomial_nonnegative(&(&k - &n - 1u32), &k);
return if (&k % 2u32).is_one() { -c } else { c };
}
binomial_nonnegative(&n, &k)
}
const KUMMER_MIN_J: u64 = 256;
const KUMMER_MAX_N: u64 = 1 << 28;
fn binomial_nonnegative(n: &BigInt, k: &BigInt) -> BigInt {
if k > n {
return BigInt::zero();
}
let j = (n - k).min(k.clone());
let Some(j) = j.to_u64() else {
return BigInt::zero();
};
if let Some(n_u64) = n.to_u64()
&& j >= KUMMER_MIN_J
&& n_u64 <= KUMMER_MAX_N
{
let (jf, nf) = (j as f64, n_u64 as f64);
let product_cost = jf * jf * (std::f64::consts::E * nf / jf).log2() / 64.0;
if product_cost > nf {
return binomial_by_prime_powers(n_u64, j);
}
}
let mut result = BigInt::one();
for i in 0..j {
result = result * (n - BigInt::from(i)) / BigInt::from(i + 1);
}
result
}
fn binomial_by_prime_powers(n: u64, j: u64) -> BigInt {
let m = n - j;
let exponent = |p: u64| -> u32 {
let (mut e, mut q) = (0u32, p);
loop {
e += u32::from(n / q - j / q - m / q > 0);
match q.checked_mul(p) {
Some(next) if next <= n => q = next,
_ => return e,
}
}
};
let mut words: Vec<BigInt> = Vec::new();
let mut acc: u64 = 1;
let mut push = |p: u64| {
let e = exponent(p);
if e == 0 {
return;
}
let f = p.pow(e);
match acc.checked_mul(f) {
Some(v) => acc = v,
None => {
words.push(BigInt::from(acc));
acc = f;
}
}
};
push(2);
let half = usize::try_from(n.div_ceil(2)).unwrap_or(0);
let mut composite = vec![0u64; half.div_ceil(64)];
let is_composite = |c: &[u64], i: usize| c[i / 64] >> (i % 64) & 1 == 1;
for i in 1..half {
if is_composite(&composite, i) {
continue;
}
let p = 2 * i as u64 + 1;
push(p);
if let Some(sq) = p.checked_mul(p).filter(|&sq| sq <= n) {
let mut idx = usize::try_from((sq - 1) / 2).unwrap_or(half);
let step = usize::try_from(p).unwrap_or(half);
while idx < half {
composite[idx / 64] |= 1 << (idx % 64);
idx += step;
}
}
}
words.push(BigInt::from(acc));
while words.len() > 1 {
let mut next = Vec::with_capacity(words.len().div_ceil(2));
let mut it = words.into_iter();
while let Some(a) = it.next() {
next.push(match it.next() {
Some(b) => a * b,
None => a,
});
}
words = next;
}
words.pop().unwrap_or_else(BigInt::one)
}
pub(crate) fn fibonacci(n: i128) -> BigInt {
let m = u64::try_from(n.unsigned_abs()).unwrap_or(u64::MAX);
let (f, _) = fibonacci_pair(m);
if n < 0 && m.is_multiple_of(2) { -f } else { f }
}
pub(crate) fn lucas(n: i128) -> BigInt {
let m = u64::try_from(n.unsigned_abs()).unwrap_or(u64::MAX);
let (f, f1) = fibonacci_pair(m);
let l: BigInt = &f1 * 2 - &f;
if n < 0 && m % 2 == 1 { -l } else { l }
}
fn fibonacci_pair(n: u64) -> (BigInt, BigInt) {
let mut a = BigInt::zero(); let mut b = BigInt::one(); let bits = 64 - n.leading_zeros();
for i in (0..bits).rev() {
let a2 = &a * (&b * 2 - &a); let b2 = &a * &a + &b * &b; if (n >> i) & 1 == 1 {
a = b2.clone();
b = a2 + b2; } else {
a = a2;
b = b2;
}
}
(a, b)
}
pub(crate) fn euler_even(m: usize) -> BigInt {
let mut s: Vec<BigInt> = Vec::with_capacity(m + 1);
s.push(BigInt::one());
for k in 1..=m {
let next = &s[k - 1] * BigInt::from(k);
s.push(next);
}
for k in 1..=m {
for j in k + 1..=m {
s[j] = &s[j - 1] * BigInt::from(j - k) + &s[j] * BigInt::from(j - k + 1);
}
}
let s_m = s.pop().unwrap_or_else(BigInt::one);
if m.is_multiple_of(2) { s_m } else { -s_m }
}
pub fn multinomial(n: impl Into<BigInt>, ks: &[impl Into<BigInt> + Clone]) -> Option<BigInt> {
let n = n.into();
if n.is_negative() {
return Some(BigInt::zero());
}
let ks_big: Vec<BigInt> = ks.iter().map(|k| k.clone().into()).collect();
let mut sum = BigInt::zero();
for k in &ks_big {
if k.is_negative() {
return Some(BigInt::zero());
}
sum += k;
}
if sum != n {
return Some(BigInt::zero());
}
let n_u64: u64 = (&n).try_into().ok()?;
let ks_u64: Vec<u64> = ks_big
.iter()
.map(|k| k.try_into().ok())
.collect::<Option<Vec<u64>>>()?;
let mut remaining = n_u64;
let mut lower_bits = 0.0;
for &k in &ks_u64 {
let j = k.min(remaining - k);
if j > 0 {
lower_bits += j as f64 * (remaining as f64 / j as f64).log2();
}
remaining -= k;
}
if lower_bits > MULTINOMIAL_MAX_BITS {
return None;
}
Some(multinomial_u64(&ks_u64))
}
const MULTINOMIAL_MAX_BITS: f64 = 4_294_967_296.0;
pub fn multinomial_u64(ks: &[u64]) -> BigInt {
let mut result = BigInt::one();
let mut remaining: u128 = ks.iter().map(|&k| u128::from(k)).sum();
for &k in ks {
if k == 0 {
continue;
}
result *= binomial_nonnegative(&BigInt::from(remaining), &BigInt::from(k));
remaining -= u128::from(k);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn factorial_matches_the_running_product() {
let mut acc = BigInt::one();
assert_eq!(factorial(0), acc);
for n in 1..=200u64 {
acc *= n;
assert_eq!(factorial(n), acc, "{n}!");
}
}
#[test]
fn binomial_symmetry_and_pascal() {
for n in 0..30i64 {
for k in 0..=n {
assert_eq!(binomial(n, k), binomial(n, n - k));
if k > 0 {
assert_eq!(
binomial(n + 1, k),
binomial(n, k - 1) + binomial(n, k),
"Pascal at ({n}, {k})"
);
}
}
}
assert_eq!(binomial(5, -1), BigInt::zero());
assert_eq!(binomial(-1, 3), BigInt::from(-1));
assert_eq!(binomial(-2, 3), BigInt::from(-4));
}
#[test]
fn prime_power_product_matches_the_running_product() {
let running = |n: u64, j: u64| -> BigInt {
let mut r = BigInt::one();
for i in 0..j {
r = r * BigInt::from(n - i) / BigInt::from(i + 1);
}
r
};
for (n, j) in [
(512u64, 256u64),
(513, 256),
(1000, 300),
(2000, 1000),
(2001, 1000),
(4096, 2048),
(10007, 5000),
(65536, 300),
] {
assert_eq!(binomial_by_prime_powers(n, j), running(n, j), "C({n}, {j})");
assert_eq!(binomial(n, j), running(n, j), "C({n}, {j})");
assert_eq!(binomial(n, n - j), running(n, j), "C({n}, {})", n - j);
}
let k = BigInt::from(1u8) << 64usize;
assert_eq!(binomial(-1, k.clone()), BigInt::one());
assert_eq!(binomial(-1, &k + 1u32), -BigInt::one());
assert_eq!(binomial(-3, &k + 1u32), -((&k + 2u32) * (&k + 3u32) / 2u32));
}
#[test]
fn multinomial_parts() {
assert_eq!(multinomial_u64(&[2, 3, 1]), BigInt::from(60));
assert_eq!(multinomial_u64(&[0, 4]), BigInt::one());
assert_eq!(multinomial(4, &[1, 1, 1, 1]), Some(BigInt::from(24)));
assert_eq!(multinomial(4, &[1, 1, 1]), Some(BigInt::zero()));
assert_eq!(multinomial(-1, &[1]), Some(BigInt::zero()));
assert_eq!(multinomial(3, &[-1, 4]), Some(BigInt::zero()));
}
}