use std::sync::{OnceLock, RwLock};
use oxinum_core::{OxiNumError, OxiNumResult};
use oxinum_int::native::BigInt;
use super::binary_splitting::{binary_split, BSSeries, BSSplit};
use super::float::{BigFloat, RoundingMode};
fn bigfloat_from_bigint(n: &BigInt, prec: u32, mode: RoundingMode) -> BigFloat {
if n.is_zero() {
return BigFloat::zero(prec);
}
BigFloat::from_parts(n.sign(), n.magnitude().clone(), 0, prec, mode)
}
struct Chudnovsky;
impl BSSeries for Chudnovsky {
fn term(&self, k: u64) -> (BigInt, BigInt, BigInt, BigInt) {
const A: i64 = 545_140_134;
const B: i64 = 13_591_409;
const C3: i64 = 640_320_i64 * 640_320 * 640_320;
let a_val = BigInt::from(A * k as i64 + B);
if k == 0 {
return (BigInt::one(), BigInt::one(), BigInt::one(), a_val);
}
let k6 = BigInt::from((k * 6) as i64);
let p_val = {
let f1 = &k6 - BigInt::from(5i64);
let f2 = &k6 - BigInt::from(4i64);
let f3 = &k6 - BigInt::from(3i64);
let f4 = &k6 - BigInt::from(2i64);
let f5 = &k6 - BigInt::from(1i64);
let f6 = k6;
let prod = &f1 * &f2 * &f3 * &f4 * &f5 * &f6;
-prod
};
let k3 = BigInt::from((k * 3) as i64);
let kb = BigInt::from(k as i64);
let trinom = &k3 * (&k3 - BigInt::from(1i64)) * (&k3 - BigInt::from(2i64));
let q_val = &trinom * &kb * &kb * &kb * BigInt::from(C3);
let b_val = BigInt::one();
(p_val, q_val, b_val, a_val)
}
}
fn chudnovsky_terms(prec: u32) -> u64 {
let n = ((prec as f64) / 47.11) as u64 + 8;
n.max(2)
}
fn compute_pi_at(work_prec: u32) -> OxiNumResult<BigFloat> {
let mode = RoundingMode::HalfEven;
let n = chudnovsky_terms(work_prec);
let split: BSSplit = binary_split(&Chudnovsky, 0, n);
const C3: i64 = 640_320_i64 * 640_320 * 640_320;
let c3_float = BigFloat::from_i64(C3, work_prec, mode);
let sqrt_c3 = c3_float.sqrt(work_prec, mode)?;
let twelve = BigInt::from(12i64);
let denom_int: BigInt = &twelve * &split.t;
let numer_int: BigInt = &split.q * &split.b;
let numer_f = bigfloat_from_bigint(&numer_int, work_prec, mode);
let denom_f = bigfloat_from_bigint(&denom_int, work_prec, mode);
let full_numer = &sqrt_c3 * &numer_f;
let pi_raw = full_numer.div_ref(&denom_f)?;
if pi_raw.sign() == oxinum_core::Sign::Negative {
return Err(OxiNumError::Precision(
"pi computation yielded negative result — Chudnovsky term encoding bug".into(),
));
}
Ok(pi_raw)
}
struct ESeries;
impl BSSeries for ESeries {
fn term(&self, k: u64) -> (BigInt, BigInt, BigInt, BigInt) {
let one = BigInt::one();
if k == 0 {
(one.clone(), one.clone(), one.clone(), one)
} else {
(one.clone(), BigInt::from(k as i64), one.clone(), one)
}
}
}
fn e_terms(work_prec: u32) -> u64 {
let target = (work_prec as f64) + 8.0;
let mut k: u64 = 4;
let mut log2_kfact: f64 = (1..=4u64).map(|i| (i as f64).log2()).sum();
while log2_kfact < target {
k += 1;
log2_kfact += (k as f64).log2();
}
k + 1
}
fn compute_e_at(work_prec: u32) -> OxiNumResult<BigFloat> {
let mode = RoundingMode::HalfEven;
let n = e_terms(work_prec);
let split: BSSplit = binary_split(&ESeries, 0, n);
let denom_int: BigInt = &split.q * &split.b;
let numer_f = bigfloat_from_bigint(&split.t, work_prec, mode);
let denom_f = bigfloat_from_bigint(&denom_int, work_prec, mode);
numer_f.div_ref(&denom_f)
}
fn atanh_inv_x(x: u64, work_prec: u32) -> OxiNumResult<BigFloat> {
let mode = RoundingMode::HalfEven;
let log2_x = (x as f64).log2();
let k_needed = (((work_prec as f64) + 8.0) / log2_x / 2.0) as u64 + 4;
let x_f = BigFloat::from_i64(x as i64, work_prec, mode);
let x2_f = &x_f * &x_f;
let one_f = BigFloat::from_i64(1, work_prec, mode);
let mut term = one_f.div_ref_with_mode(&x_f, mode)?;
let mut acc = term.clone();
for k in 1..=k_needed {
term = term.div_ref_with_mode(&x2_f, mode)?;
let denom_f = BigFloat::from_i64((2 * k + 1) as i64, work_prec, mode);
let scaled = term.div_ref_with_mode(&denom_f, mode)?;
acc = &acc + &scaled;
}
Ok(acc)
}
fn compute_ln2_at(work_prec: u32) -> OxiNumResult<BigFloat> {
let mode = RoundingMode::HalfEven;
let a31 = atanh_inv_x(31, work_prec)?;
let a49 = atanh_inv_x(49, work_prec)?;
let a161 = atanh_inv_x(161, work_prec)?;
let c14 = BigFloat::from_i64(14, work_prec, mode);
let c10 = BigFloat::from_i64(10, work_prec, mode);
let c6 = BigFloat::from_i64(6, work_prec, mode);
let t1 = &c14 * &a31;
let t2 = &c10 * &a49;
let t3 = &c6 * &a161;
Ok(&(&t1 + &t2) + &t3)
}
struct ConstCache {
inner: OnceLock<RwLock<Option<BigFloat>>>,
}
impl ConstCache {
const fn new() -> Self {
Self {
inner: OnceLock::new(),
}
}
fn lock(&self) -> &RwLock<Option<BigFloat>> {
self.inner.get_or_init(|| RwLock::new(None))
}
fn get_or_compute<F>(&self, prec: u32, compute: F) -> OxiNumResult<BigFloat>
where
F: FnOnce(u32) -> OxiNumResult<BigFloat>,
{
const MODE: RoundingMode = RoundingMode::HalfEven;
{
let guard = self
.lock()
.read()
.map_err(|_| OxiNumError::Precision("ConstCache RwLock poisoned".into()))?;
if let Some(ref cached) = *guard {
if cached.precision() >= prec + 16 {
return Ok(cached.clone().with_precision(prec, MODE));
}
}
}
let work_prec = prec + 32;
let result = compute(work_prec)?;
{
let mut guard = self
.lock()
.write()
.map_err(|_| OxiNumError::Precision("ConstCache RwLock poisoned".into()))?;
if let Some(ref cached) = *guard {
if cached.precision() >= prec + 16 {
return Ok(cached.clone().with_precision(prec, MODE));
}
}
*guard = Some(result);
}
{
let guard = self
.lock()
.read()
.map_err(|_| OxiNumError::Precision("ConstCache RwLock poisoned".into()))?;
if let Some(ref cached) = *guard {
return Ok(cached.clone().with_precision(prec, MODE));
}
}
Err(OxiNumError::Precision(
"ConstCache: unexpected empty after write".into(),
))
}
}
static PI_CACHE: ConstCache = ConstCache::new();
static E_CACHE: ConstCache = ConstCache::new();
static LN2_CACHE: ConstCache = ConstCache::new();
pub fn pi(prec: u32) -> OxiNumResult<BigFloat> {
PI_CACHE.get_or_compute(prec, compute_pi_at)
}
pub fn e_const(prec: u32) -> OxiNumResult<BigFloat> {
E_CACHE.get_or_compute(prec, compute_e_at)
}
pub fn ln2(prec: u32) -> OxiNumResult<BigFloat> {
LN2_CACHE.get_or_compute(prec, compute_ln2_at)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chudnovsky_term_zero() {
let (p, q, b, a) = Chudnovsky.term(0);
assert_eq!(p, BigInt::one());
assert_eq!(q, BigInt::one());
assert_eq!(b, BigInt::one());
assert_eq!(a, BigInt::from(13_591_409i64));
}
#[test]
fn chudnovsky_term_one_negative_p() {
let (p, _q, _b, _a) = Chudnovsky.term(1);
assert_eq!(p, BigInt::from(-720i64));
}
#[test]
fn e_series_term() {
let (p, q, b, a) = ESeries.term(0);
assert_eq!(p, BigInt::one());
assert_eq!(q, BigInt::one());
assert_eq!(b, BigInt::one());
assert_eq!(a, BigInt::one());
let (p2, q2, _b2, a2) = ESeries.term(5);
assert_eq!(p2, BigInt::one());
assert_eq!(q2, BigInt::from(5i64));
assert_eq!(a2, BigInt::one());
}
#[test]
fn pi_f64_matches() {
let p = pi(64).expect("pi(64)");
assert!((p.to_f64() - std::f64::consts::PI).abs() < 1e-14);
}
#[test]
fn e_f64_matches() {
let e = e_const(64).expect("e_const(64)");
assert!((e.to_f64() - std::f64::consts::E).abs() < 1e-14);
}
#[test]
fn ln2_f64_matches() {
let l = ln2(64).expect("ln2(64)");
assert!((l.to_f64() - std::f64::consts::LN_2).abs() < 1e-14);
}
}