use astro_float::{BigFloat, Consts, RoundingMode, WORD_BIT_SIZE};
use num_bigint::{BigInt, BigUint};
use num_rational::Ratio;
use num_traits::{One, Zero};
use parking_lot::Mutex;
use crate::base::numeric::{Q, bigint_to_bigfloat};
static BERNOULLI_CACHE: Mutex<Vec<Q>> = Mutex::new(Vec::new());
const TABLE_MAX_K: usize = 2048;
pub(crate) const EXACT_EULER_N: usize = 512;
#[must_use]
pub(crate) fn bernoulli(n: usize) -> Q {
match n {
0 => Ratio::one(),
1 => Ratio::new(BigInt::from(-1), BigInt::from(2)),
_ if n % 2 == 1 => Ratio::zero(),
_ => even(n / 2),
}
}
#[must_use]
pub(crate) fn bernoulli_single(n: usize) -> Q {
if n < SINGLE_FROM || !n.is_multiple_of(2) {
return bernoulli(n);
}
let cached = BERNOULLI_CACHE.lock().get(n / 2 - 1).cloned();
if let Some(b) = cached {
return b;
}
bernoulli_by_zeta(n).unwrap_or_else(|| bernoulli(n))
}
const SINGLE_FROM: usize = 512;
fn tangent_numbers(n: usize) -> Vec<BigInt> {
let mut t: Vec<BigInt> = vec![BigInt::zero(); n + 1];
if n == 0 {
return t;
}
t[1] = BigInt::one();
for k in 2..=n {
t[k] = &t[k - 1] * BigInt::from(k - 1);
}
for k in 2..=n {
for j in k..=n {
let v = &t[j - 1] * BigInt::from(j - k) + &t[j] * BigInt::from(j - k + 2);
t[j] = v;
}
}
t
}
#[must_use]
pub(crate) fn even(k: usize) -> Q {
if k == 0 {
return Q::one();
}
if k > TABLE_MAX_K {
let cached = BERNOULLI_CACHE.lock().get(k - 1).cloned();
if let Some(b) = cached {
return b;
}
if let Some(b) = bernoulli_by_zeta(2 * k) {
return b;
}
}
let mut cache = BERNOULLI_CACHE.lock();
if cache.len() < k {
let n = k.max(cache.len() + cache.len() / 4).max(32);
let t = tangent_numbers(n);
let mut out = Vec::with_capacity(n);
for (i, tk) in t.iter().enumerate().skip(1) {
let two_k = 2 * i;
let p = BigInt::one() << two_k;
let den = &p * (&p - BigInt::one());
let mut num = tk * BigInt::from(two_k);
if i.is_multiple_of(2) {
num = -num;
}
out.push(Ratio::new(num, den));
}
tracing::trace!(count = n, "bernoulli: tangent numbers computed");
*cache = out;
}
cache[k - 1].clone()
}
fn primes_to(limit: usize) -> Vec<usize> {
let mut composite = vec![false; limit + 1];
let mut out = Vec::new();
for p in 2..=limit {
if composite[p] {
continue;
}
out.push(p);
let mut m = p.saturating_mul(p);
while m <= limit {
composite[m] = true;
m += p;
}
}
out
}
fn is_small_prime(m: usize) -> bool {
if m < 2 {
return false;
}
let mut d = 2usize;
while d.saturating_mul(d) <= m {
if m.is_multiple_of(d) {
return false;
}
d += 1;
}
true
}
fn log2_factorial_up(n: usize) -> f64 {
if n < 2 {
return 0.0;
}
let x = n as f64;
let ln = x * x.ln() - x + 0.5 * (2.0 * std::f64::consts::PI * x).ln() + 1.0 / (12.0 * x);
ln * std::f64::consts::LOG2_E * (1.0 + 1e-12) + 1.0
}
fn euler_product(
s: usize,
wp: usize,
chi: impl Fn(usize) -> i32,
rm: RoundingMode,
) -> Option<BigFloat> {
if s < 2 {
return None;
}
let exponent = (wp as f64 + 8.0) / (s as f64 - 1.0);
if exponent > 24.0 {
return None; }
let limit = (exponent.exp2().ceil() as usize).max(3);
let mut prod = BigFloat::from_i32(1, wp);
for p in primes_to(limit) {
let c = chi(p);
if c == 0 {
continue;
}
let below = (s as f64) * (p as f64).log2();
if below > wp as f64 + 8.0 {
break;
}
let q = (wp as f64 - below.floor()) as usize + 32;
let guard = usize::BITS as usize - s.leading_zeros() as usize + 16;
let t = BigFloat::from_u64(p as u64, 64).powi(s, q + guard, rm);
let t = BigFloat::from_i32(1, 64).div(&t, q, rm);
let delta = prod.mul(&t, q, rm);
prod = if c > 0 {
prod.sub(&delta, wp, rm)
} else {
prod.add(&delta, wp, rm)
};
}
Some(prod)
}
fn nearest_integer(x: &BigFloat) -> Option<BigInt> {
let (words, _, _, exponent, _) = x.as_raw_parts()?;
let mut digits: Vec<u32> = Vec::with_capacity(words.len() * 2);
for &w in words {
let mut w = w as u128;
for _ in 0..WORD_BIT_SIZE / 32 {
digits.push((w & 0xFFFF_FFFF) as u32);
w >>= 32;
}
}
let mantissa = BigUint::new(digits);
let len = (words.len() * WORD_BIT_SIZE) as i64;
let shift = len - i64::from(exponent);
if shift <= 0 {
let shift = usize::try_from(-shift).ok()?;
return Some(BigInt::from(mantissa << shift));
}
let shift = usize::try_from(shift).ok()?;
if shift < 18 {
return None;
}
let one = BigUint::one();
let rounded = (&mantissa + (&one << (shift - 1))) >> shift;
let back = &rounded << shift;
let gap = if back >= mantissa {
back - &mantissa
} else {
&mantissa - back
};
(gap < (one << (shift - 16))).then(|| BigInt::from(rounded))
}
fn factorial(n: usize) -> BigInt {
fn range(lo: usize, hi: usize) -> BigInt {
if hi - lo <= 16 {
return (lo..hi).fold(BigInt::one(), |acc, i| acc * BigInt::from(i));
}
let mid = lo + (hi - lo) / 2;
range(lo, mid) * range(mid, hi)
}
range(1, n + 1)
}
fn working_bits(bits: f64, ops: usize) -> usize {
(bits.max(0.0).ceil() as usize) + 64 + (usize::BITS - ops.leading_zeros()) as usize + 8
}
#[must_use]
pub(crate) fn bernoulli_by_zeta(n: usize) -> Option<Q> {
if n < 2 || !n.is_multiple_of(2) {
return None;
}
let mut divisors = Vec::new();
let mut d = 1usize;
while d.saturating_mul(d) <= n {
if n.is_multiple_of(d) {
divisors.push(d);
if d != n / d {
divisors.push(n / d);
}
}
d += 1;
}
let mut den = BigInt::one();
for e in divisors {
if is_small_prime(e + 1) {
den *= BigInt::from(e + 1);
}
}
let rm = RoundingMode::ToEven;
let lg_den = den.bits() as f64;
let bits = 2.0 + log2_factorial_up(n) - n as f64 * (2.0 * std::f64::consts::PI).log2() + lg_den;
let wp = working_bits(bits, 8 * n + 64);
let mut cc = Consts::new().ok()?;
let product = euler_product(n, wp, |_| 1, rm)?; let two_pi = cc.pi(wp, rm).mul(&BigFloat::from_i32(2, 64), wp, rm);
let power = two_pi.powi(n, wp, rm);
let x = bigint_to_bigfloat(&factorial(n), wp)
.mul(&bigint_to_bigfloat(&den, wp), wp, rm)
.mul(&BigFloat::from_i32(2, 64), wp, rm)
.div(&power.mul(&product, wp, rm), wp, rm);
let num = nearest_integer(&x)?;
let num = if n.is_multiple_of(4) { -num } else { num };
Some(Ratio::new_raw(num, den))
}
#[must_use]
pub(crate) fn euler_by_beta(n: usize) -> Option<BigInt> {
if !n.is_multiple_of(2) {
return Some(BigInt::zero());
}
if n == 0 {
return Some(BigInt::one());
}
let rm = RoundingMode::ToEven;
let s = n + 1;
let bits = 1.0 + log2_factorial_up(n) + s as f64 * (2.0 / std::f64::consts::PI).log2() + 1.0;
let wp = working_bits(bits, 8 * n + 64);
let mut cc = Consts::new().ok()?;
let chi = |p: usize| match p % 4 {
1 => 1,
3 => -1,
_ => 0,
};
let product = euler_product(s, wp, chi, rm)?;
let pi = cc.pi(wp, rm).clone();
let power = pi.powi(s, wp, rm);
let mut two_pow = BigFloat::from_i32(1, 64);
two_pow.set_exponent(i32::try_from(s + 2).ok()?); let x = bigint_to_bigfloat(&factorial(n), wp)
.mul(&two_pow, wp, rm)
.div(&power.mul(&product, wp, rm), wp, rm);
let e = nearest_integer(&x)?;
Some(if n.is_multiple_of(4) { e } else { -e })
}
#[cfg(test)]
mod tests {
use super::*;
use num_traits::ToPrimitive;
#[test]
fn b0_is_one() {
let b0 = bernoulli(0);
assert_eq!(b0, Ratio::one(), "B_0 = 1");
}
#[test]
fn b1_is_neg_half() {
let b1 = bernoulli(1);
let expected = Ratio::new(BigInt::from(-1), BigInt::from(2));
assert_eq!(b1, expected, "B_1 = -1/2");
}
#[test]
fn b2_is_one_sixth() {
let b2 = bernoulli(2);
let expected = Ratio::new(BigInt::from(1), BigInt::from(6));
assert_eq!(b2, expected, "B_2 = 1/6");
}
#[test]
fn b4_is_neg_one_thirtieth() {
let b4 = bernoulli(4);
let expected = Ratio::new(BigInt::from(-1), BigInt::from(30));
assert_eq!(b4, expected, "B_4 = -1/30");
}
#[test]
fn b6_is_one_forty_second() {
let b6 = bernoulli(6);
let expected = Ratio::new(BigInt::from(1), BigInt::from(42));
assert_eq!(b6, expected, "B_6 = 1/42");
}
#[test]
fn b8_is_neg_one_thirtieth() {
let b8 = bernoulli(8);
let expected = Ratio::new(BigInt::from(-1), BigInt::from(30));
assert_eq!(b8, expected, "B_8 = -1/30");
}
#[test]
fn b10() {
let b10 = bernoulli(10);
let expected = Ratio::new(BigInt::from(5), BigInt::from(66));
assert_eq!(b10, expected, "B_10 = 5/66");
}
#[test]
fn b12() {
let b12 = bernoulli(12);
let expected = Ratio::new(BigInt::from(-691), BigInt::from(2730));
assert_eq!(b12, expected, "B_12 = -691/2730");
}
#[test]
fn odd_bernoulli_numbers_are_zero() {
for n in [3, 5, 7, 9, 11, 13, 15, 17, 19, 21] {
let bn = bernoulli(n);
assert!(bn.is_zero(), "B_{n} should be 0, got {bn}");
}
}
#[test]
fn b14() {
let b14 = bernoulli(14);
let expected = Ratio::new(BigInt::from(7), BigInt::from(6));
assert_eq!(b14, expected, "B_14 = 7/6");
}
#[test]
fn b16() {
let b16 = bernoulli(16);
let expected = Ratio::new(BigInt::from(-3617), BigInt::from(510));
assert_eq!(b16, expected, "B_16 = -3617/510");
}
#[test]
fn b18() {
let b18 = bernoulli(18);
let expected = Ratio::new(BigInt::from(43867), BigInt::from(798));
assert_eq!(b18, expected, "B_18 = 43867/798");
}
#[test]
fn b20() {
let b20 = bernoulli(20);
let expected = Ratio::new(BigInt::from(-174611), BigInt::from(330));
assert_eq!(b20, expected, "B_20 = -174611/330");
}
#[test]
fn cache_is_reused() {
let first = bernoulli(30);
let second = bernoulli(30);
assert_eq!(first, second, "cache should return identical values");
}
#[test]
fn large_index_computable() {
let b50 = bernoulli(50);
assert!(!b50.is_zero(), "B_50 should be nonzero");
assert_eq!(
b50.denom(),
&BigInt::from(66),
"B_50 denominator should be 66"
);
}
#[test]
fn tangent_numbers_match_the_rational_recurrence() {
let mut b: Vec<Q> = vec![Ratio::one()];
for m in 1..=300usize {
let mut sum = Ratio::zero();
let mut binom: Q = Ratio::one();
for (k, bk) in b.iter().enumerate() {
sum += &binom * bk;
binom = binom * Ratio::from_integer(BigInt::from(m + 1 - k))
/ Ratio::from_integer(BigInt::from(k + 1));
}
b.push(-sum / Ratio::from_integer(BigInt::from(m + 1)));
}
for (m, bm) in b.iter().enumerate() {
assert_eq!(bernoulli(m), *bm, "B_{m}");
}
}
#[test]
fn zeta_and_beta_methods_match_the_tables() {
for n in (20..=240).step_by(2).chain([510, 512, 514]) {
assert_eq!(bernoulli_by_zeta(n), Some(bernoulli(n)), "B_{n}");
}
for n in (16..=200).step_by(2).chain([512, 514]) {
let want = crate::base::combinatorics::euler_even(n / 2);
assert_eq!(euler_by_beta(n), Some(want), "E_{n}");
}
assert_eq!(bernoulli_single(514), bernoulli(514));
assert_eq!(euler_by_beta(7), Some(BigInt::zero()));
}
#[test]
fn alternating_signs_for_even() {
for k in 1..=15 {
let b = bernoulli(2 * k);
let f = b.to_f64().unwrap();
if k % 2 == 1 {
assert!(f > 0.0, "B_{} should be positive, got {}", 2 * k, f);
} else {
assert!(f < 0.0, "B_{} should be negative, got {}", 2 * k, f);
}
}
}
}