use crate::float::{Float, RoundingMode};
use crate::float_mp_consts as c;
use crate::int::Int;
use crate::rational::Rational;
const NEAR: RoundingMode = RoundingMode::Nearest;
const PRIMES: [u64; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
pub(crate) fn exp_mp(x: &Float, precision: u64, mode: RoundingMode) -> Option<Float> {
if precision < 384 || precision + 192 > c::PLOG_BITS {
return None;
}
let n = precision + precision.isqrt() + 48;
let ln2 = crate::float::ln2_embedded(n)?;
let k = round_to_int(&x.div(&ln2, n, NEAR));
let r = x.sub(&Float::from_int(&k, n, NEAR).mul(&ln2, n, NEAR), n, NEAR);
let r_scaled = round_scaled(&r, c::MP_SCALE);
let cf = babai_f64(&r_scaled)?;
let mut e = [0i64; 12];
for (i, &ci) in cf.iter().enumerate() {
for (j, ej) in e.iter_mut().enumerate() {
*ej += ci * c::MP_BASIS[i][j];
}
}
if e.iter().any(|&v| v.unsigned_abs() > 1 << 20) {
return None;
}
const SIGS: [&[u64]; 11] = [
&c::LN_3,
&c::LN_5,
&c::LN_7,
&c::LN_11,
&c::LN_13,
&c::LN_17,
&c::LN_19,
&c::LN_23,
&c::LN_29,
&c::LN_31,
&c::LN_37,
];
let mut sum = Float::from_int(&Int::ZERO, n, NEAR);
for (j, &ej) in e.iter().enumerate() {
if ej == 0 {
continue;
}
let l = if j == 0 {
ln2.clone()
} else {
crate::float::round_const_bits(SIGS[j - 1], c::PLOG_BITS, n)
};
sum = sum.add(
&Float::from_int(&Int::from_i64(ej), n, NEAR).mul(&l, n, NEAR),
n,
NEAR,
);
}
let t = r.sub(&sum, n, NEAR);
let big_t = scaled_trunc(&t, n as i64);
let (expt, terms) = exp_series(&big_t, n);
let (mut num, mut den) = (Int::ONE, Int::ONE);
for (j, &ej) in e.iter().enumerate().skip(1) {
if ej > 0 {
num = num.mul(&Int::from_u64(PRIMES[j]).pow(ej as u32));
} else if ej < 0 {
den = den.mul(&Int::from_u64(PRIMES[j]).pow((-ej) as u32));
}
}
let two_exp = k.to_i64().unwrap_or(0) + e[0] - n as i64;
let scaled_num = expt.mul(&num);
let err = Int::from_i64(terms as i64 + 4).mul(&num);
let lo = crate::float::round_ratio(&scaled_num.sub(&err), &den, two_exp, precision, mode);
let hi = crate::float::round_ratio(&scaled_num.add(&err), &den, two_exp, precision, mode);
(lo == hi).then_some(lo)
}
#[inline]
fn round_f64(x: f64) -> f64 {
(x + if x >= 0.0 { 0.5 } else { -0.5 }) as i64 as f64
}
#[allow(clippy::needless_range_loop)] fn babai_f64(r_scaled: &Int) -> Option<[i64; 12]> {
let dim = 13;
let rt = r_scaled.to_i64()? as f64; let b: [[f64; 13]; 12] =
core::array::from_fn(|i| core::array::from_fn(|j| c::MP_BASIS[i][j] as f64));
let mut bstar = b;
let dotf = |a: &[f64; 13], c: &[f64; 13]| a.iter().zip(c).map(|(x, y)| x * y).sum::<f64>();
for i in 0..12 {
for j in 0..i {
let mu = dotf(&b[i], &bstar[j]) / dotf(&bstar[j], &bstar[j]);
for t in 0..dim {
bstar[i][t] -= mu * bstar[j][t];
}
}
}
let norm2: [f64; 12] = core::array::from_fn(|i| dotf(&bstar[i], &bstar[i]));
let mut bv = [0.0f64; 13];
bv[12] = rt;
let mut coeffs = [0i64; 12];
for i in (0..12).rev() {
let ci = round_f64(dotf(&bv, &bstar[i]) / norm2[i]);
for t in 0..dim {
bv[t] -= ci * b[i][t];
}
coeffs[i] = ci as i64;
}
Some(coeffs)
}
fn exp_series(big_t: &Int, n: u64) -> (Int, u64) {
let mut sum = Int::ONE.mul_2k(n as u32);
let mut term = sum.clone();
let mut kk = 1i64;
loop {
term = term
.mul(big_t)
.div_2k_trunc(n as u32)
.div_trunc(&Int::from_i64(kk));
if term.is_zero() {
break;
}
sum = sum.add(&term);
kk += 1;
}
(sum, kk as u64)
}
fn round_to_int(f: &Float) -> Int {
match f.to_rational() {
Some(r) => round_scaled_rat(&r, 0),
None => Int::ZERO,
}
}
fn round_scaled(f: &Float, s: u64) -> Int {
match f.to_rational() {
Some(r) => round_scaled_rat(&r, s),
None => Int::ZERO,
}
}
fn round_scaled_rat(r: &Rational, s: u64) -> Int {
let num = r.numerator().mul_2k(s 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,
}
}