use alloc::vec::Vec;
use crate::float::{Float, RoundingMode};
use crate::int::Int;
use crate::rational::Rational;
const NEAR: RoundingMode = RoundingMode::Nearest;
#[derive(Clone, Default)]
pub struct FloatContext {
logs: Vec<(u64, u64, Float)>,
}
impl FloatContext {
pub fn new() -> FloatContext {
FloatContext::default()
}
fn ln_prime(&mut self, p: u64, w: u64) -> Float {
if let Some((_, _, f)) = self.logs.iter().find(|(pp, pw, _)| *pp == p && *pw >= w) {
return f.clone();
}
let f = Float::from_int(&Int::from_u64(p), w, NEAR).ln(w, NEAR);
self.logs.retain(|(pp, _, _)| *pp != p);
self.logs.push((p, w, f.clone()));
f
}
pub fn exp(&mut self, x: &Float, precision: u64, mode: RoundingMode) -> Float {
if x.is_zero() {
return Float::from_int(&Int::ONE, precision, mode);
}
let w0 = precision + 64;
let ln2_0 = self.ln_prime(2, w0);
let k = round_to_int(&x.div(&ln2_0, w0, NEAR));
let j = precision.isqrt().max(1);
let n = precision + j + k.bit_len() as u64 + 16;
let ln2 = self.ln_prime(2, n);
let r = x.sub(&Float::from_int(&k, n, NEAR).mul(&ln2, n, NEAR), n, NEAR);
let big_r = scaled_trunc(&r, n as i64 - j as i64);
let sum = exp_series_scaled(&big_r, n, j);
scaled_to_float(&sum, k.to_i64().unwrap_or(0) - n as i64, precision, mode)
}
}
fn exp_series_scaled(big_r: &Int, n: u64, j: u64) -> Int {
let mut sum = Int::ONE.mul_2k(n as u32);
let mut term = sum.clone();
let mut kk = 1i64;
loop {
term = term
.mul(big_r)
.div_2k_trunc(n as u32)
.div_trunc(&Int::from_i64(kk));
if term.is_zero() {
break;
}
sum = sum.add(&term);
kk += 1;
}
for _ in 0..j {
sum = sum.square().div_2k_trunc(n as u32);
}
sum
}
fn round_to_int(f: &Float) -> Int {
match f.to_rational() {
Some(r) => round_scaled_rat(&r, 0),
None => Int::ZERO,
}
}
fn round_scaled_rat(r: &Rational, n: u64) -> Int {
let num = r.numerator().mul_2k(n as u32);
let den = r.denominator();
let two = Int::from_i64(2);
num.mul(&two).add(den).div_floor(&den.mul(&two))
}
fn scaled_trunc(f: &Float, m: i64) -> Int {
match f.to_rational() {
Some(r) => {
let (num, den) = (r.numerator(), r.denominator());
if m >= 0 {
num.mul_2k(m as u32).div_trunc(den)
} else {
num.div_trunc(&den.mul_2k((-m) as u32))
}
}
None => Int::ZERO,
}
}
fn scaled_to_float(sum: &Int, exp2: i64, precision: u64, mode: RoundingMode) -> Float {
let val = if exp2 >= 0 {
Rational::from_integer(sum.mul_2k(exp2 as u32))
} else {
Rational::new(sum.clone(), Int::ONE.mul_2k((-exp2) as u32))
};
Float::from_rational(&val, precision, mode)
}
#[cfg(test)]
mod tests {
use super::*;
use core::str::FromStr;
fn ff(s: &str) -> Float {
Float::from_str(s).unwrap()
}
#[test]
fn exp_matches_random() {
let mut seed = 0x00E7_51EDu64;
let mut ctx = FloatContext::new();
for &prec in &[120u64, 400] {
for _ in 0..200 {
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
let whole = (seed >> 40) as i64 % 60 - 30;
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
let frac = (seed >> 40) % 1_000_000;
let x = Float::from_str(&alloc::format!("{whole}.{frac:06}")).unwrap();
assert_eq!(
ctx.exp(&x, prec, NEAR),
x.exp(prec, NEAR),
"exp mismatch at {x:?}"
);
}
}
}
#[test]
fn exp_matches_float_exp() {
let prec = 200u64;
let mut ctx = FloatContext::new();
for s in [
"0.5", "-0.5", "1", "-1", "3.25", "-4.75", "12.5", "-0.001", "50",
] {
assert_eq!(
ctx.exp(&ff(s), prec, NEAR),
ff(s).exp(prec, NEAR),
"exp({s})"
);
}
}
}