use crate::int::types::compute_limbs::ComputeLimbs;
use crate::int::types::traits::BigInt;
use crate::support::rounding::RoundingMode;
pub(crate) const SERIES_CAP: u128 = 20_000;
#[inline]
pub(crate) fn lit<S: BigInt>(n: i128) -> S {
S::from_i128(n)
}
#[inline]
pub(crate) fn zero<S: BigInt>() -> S {
S::ZERO
}
#[inline]
fn abs<S: BigInt>(v: S) -> S {
if v < S::ZERO { -v } else { v }
}
#[inline]
pub(crate) fn pow10<S: BigInt>(n: u32) -> S {
crate::consts::pow10::dispatch::<S>(n)
}
#[inline]
pub(crate) fn one<S: BigInt>(w: u32) -> S {
pow10::<S>(w)
}
pub(crate) fn bit_length<S: BigInt>(v: S) -> u32 {
<S as BigInt>::BITS - abs(v).leading_zeros()
}
pub(crate) fn unpack_mag<S: BigInt>(v: S, dst: &mut [u64])
where
S::Scratch: ComputeLimbs,
{
let mut tmp = <S::Scratch as ComputeLimbs>::single_u128();
v.mag_into_u128(tmp.as_mut());
let mut i = 0;
for &x in tmp.as_ref() {
if i < dst.len() {
dst[i] = x as u64;
i += 1;
}
if i < dst.len() {
dst[i] = (x >> 64) as u64;
i += 1;
}
}
}
pub(crate) fn div_rem_exact<S: BigInt>(n: S, d: S) -> (S, S)
where
S::Scratch: ComputeLimbs,
{
use crate::int::policy::div_rem::{select_for_limbs, Algorithm};
let n_neg = n < S::ZERO;
let d_neg = d < S::ZERO;
let mut nbuf = <S::Scratch as ComputeLimbs>::single_u64();
let mut dbuf = <S::Scratch as ComputeLimbs>::single_u64();
unpack_mag(abs(n), nbuf.as_mut());
unpack_mag(abs(d), dbuf.as_mut());
let mut qbuf = <S::Scratch as ComputeLimbs>::single_u64();
let mut rbuf = <S::Scratch as ComputeLimbs>::single_u64();
match select_for_limbs(nbuf.as_ref(), dbuf.as_ref()) {
Algorithm::Rem => crate::int::algos::div::div_rem::div_rem(
nbuf.as_ref(),
dbuf.as_ref(),
qbuf.as_mut(),
rbuf.as_mut(),
),
_ => {
let mut u = <S::Scratch as ComputeLimbs>::single_buffered_u64();
let mut v = <S::Scratch as ComputeLimbs>::single_buffered_u64();
crate::int::algos::div::div_knuth::div_knuth_into(
nbuf.as_ref(),
dbuf.as_ref(),
qbuf.as_mut(),
rbuf.as_mut(),
u.as_mut(),
v.as_mut(),
);
}
}
let q = S::from_mag_sign_u64(qbuf.as_ref(), n_neg != d_neg);
let r = S::from_mag_sign_u64(rbuf.as_ref(), n_neg);
(q, r)
}
#[inline]
pub(crate) fn round_div<S: BigInt>(n: S, d: S) -> S
where
S::Scratch: ComputeLimbs,
{
let (q, r) = div_rem_exact(n, d);
if r == S::ZERO {
return q;
}
let ar = abs(r);
let comp = abs(d) - ar;
let cmp_r = if ar < comp {
::core::cmp::Ordering::Less
} else if ar > comp {
::core::cmp::Ordering::Greater
} else {
::core::cmp::Ordering::Equal
};
let q_is_odd = q.bit(0);
let result_positive = (n < S::ZERO) == (d < S::ZERO);
if crate::support::rounding::should_bump(
RoundingMode::HalfToEven,
cmp_r,
q_is_odd,
result_positive,
) {
if result_positive {
q + S::ONE
} else {
q - S::ONE
}
} else {
q
}
}
#[inline]
pub(crate) fn round_div_pow10<S: BigInt>(n: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
if w == 0 {
return n;
}
if w <= 38 {
return crate::algos::support::mg_divide::div_wide_pow10::<S>(
n,
w,
RoundingMode::HalfToEven,
);
}
crate::algos::support::rescale::dispatch_wide_pow10::<S>(
n,
w,
RoundingMode::HalfToEven,
)
}
#[inline]
pub(crate) fn mul<S: BigInt>(a: S, b: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
round_div_pow10(a.wrapping_mul_low_u128(b), w)
}
#[inline]
fn mul_cached<S: BigInt>(a: S, b: S, pow10_w: S) -> S
where
S::Scratch: ComputeLimbs,
{
round_div(a.wrapping_mul_low_u128(b), pow10_w)
}
#[inline]
pub(crate) fn div_cached<S: BigInt>(a: S, b: S, pow10_w: S) -> S
where
S::Scratch: ComputeLimbs,
{
round_div(a.wrapping_mul_low_u128(pow10_w), b)
}
#[inline]
fn mul_u<S: BigInt>(a: S, n: u128) -> S {
if n <= u64::MAX as u128 {
a.mul_u64(n as u64)
} else {
a * S::from_i128(n as i128)
}
}
#[inline]
pub(crate) fn scale_by_k<S: BigInt>(c: S, k: i128) -> S {
if k >= 0 {
mul_u(c, k as u128)
} else {
-mul_u(c, k.unsigned_abs())
}
}
pub(crate) fn round_to_nearest_int<S: BigInt>(v: S, w: u32) -> i128
where
S::Scratch: ComputeLimbs,
{
let divisor = pow10::<S>(w);
let (q, r) = div_rem_exact(v, divisor);
let half = divisor >> 1;
let qi = if abs(r) >= half {
if v < S::ZERO { q - S::ONE } else { q + S::ONE }
} else {
q
};
crate::int::types::traits::BigInt::to_i128(qi)
}
fn ln2<S: BigInt>(w: u32) -> S {
crate::consts::ln2_by_working_scale::<S>(w, RoundingMode::HalfToEven)
}
pub(crate) fn sqrt_fixed<S: BigInt>(v: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
let av = abs::<S>(v);
let n = av * pow10::<S>(w);
if n <= zero::<S>() {
return zero::<S>();
}
let seed = crate::algos::support::seed_bridge::sqrt_seed_w::<S>(n);
let x0 = if seed <= zero::<S>() { lit::<S>(1) } else { seed };
let mut x = (x0 + div_rem_exact(n, x0).0) >> 1;
loop {
let y = (x + div_rem_exact(n, x).0) >> 1;
if y >= x {
return x;
}
x = y;
}
}
pub(crate) fn ln_fixed<S: BigInt>(v_w: S, w: u32, ln2_w: S) -> S
where
S::Scratch: ComputeLimbs,
{
let one_w = one::<S>(w);
let two_w = one_w + one_w;
let pow10_w = one_w;
let mut k: i32 = bit_length::<S>(v_w) as i32 - bit_length::<S>(one_w) as i32;
let mut m_w = loop {
let m = if k >= 0 {
v_w >> (k as u32)
} else {
v_w << ((-k) as u32)
};
if m >= two_w {
k += 1;
} else if m < one_w {
k -= 1;
} else {
break m;
}
};
if m_w == one_w {
return scale_by_k::<S>(ln2_w, k as i128);
}
let p_bits = w.saturating_mul(3).saturating_add(1);
let sqrt_l: u32 = {
let mut n: u32 = 0;
while (n + 1) * (n + 1) <= p_bits {
n += 1;
}
n / 4
};
let mut i = 0;
while i < sqrt_l {
m_w = sqrt_fixed::<S>(m_w, w);
i += 1;
}
let t = div_cached::<S>(m_w - one_w, m_w + one_w, pow10_w);
let t2 = mul::<S>(t, t, w);
let mut sum = t;
let mut term = t;
let mut j: u128 = 1;
loop {
term = mul::<S>(term, t2, w);
let contrib = term / lit::<S>((2 * j + 1) as i128);
if contrib == zero::<S>() {
break;
}
sum = sum + contrib;
j += 1;
if j > SERIES_CAP {
break;
}
}
let ln_m = sum << (sqrt_l + 1);
scale_by_k::<S>(ln2_w, k as i128) + ln_m
}
pub(crate) fn log1p_fixed<S: BigInt>(t: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
let one_w = one::<S>(w);
let two_w = one_w + one_w;
let pow10_w = one_w;
let u = div_cached::<S>(t, two_w + t, pow10_w);
let u2 = mul::<S>(u, u, w);
let mut sum = u;
let mut term = u;
let mut j: u128 = 1;
loop {
term = mul::<S>(term, u2, w);
let contrib = term / lit::<S>((2 * j + 1) as i128);
if contrib == zero::<S>() {
break;
}
sum = sum + contrib;
j += 1;
if j > SERIES_CAP {
break;
}
}
sum + sum
}
enum ArgRegime {
Fits,
Overflow,
Underflow,
}
fn arg_regime<S: BigInt>(v_w: S, w: u32) -> ArgRegime {
if v_w == S::ZERO {
return ArgRegime::Fits;
}
let bl = bit_length::<S>(v_w) as u64;
let w_bits = ((w as u64) * 33220).div_ceil(10000);
let bits_of = |x: u64| 64 - x.leading_zeros() as u64;
if v_w > S::ZERO {
let bits_ln2 = (<S as BigInt>::BITS as u64) * 6932 / 10000 + 1;
let w_ln10 = (w as u64) * 23025 / 10000;
let r = bits_ln2.saturating_sub(w_ln10).max(1);
if bl >= w_bits + bits_of(r) + 2 {
return ArgRegime::Overflow;
}
} else {
let u = ((w as u64) + 1) * 23026 / 10000 + 1;
if bl >= w_bits + bits_of(u) + 2 {
return ArgRegime::Underflow;
}
}
ArgRegime::Fits
}
pub(crate) fn exp_internal_peak_bits<S: BigInt>(v_w: S, w: u32) -> u64 {
if !matches!(arg_regime::<S>(v_w, w), ArgRegime::Fits) {
return u64::MAX;
}
let one_w_pre = one::<S>(w);
let l2_pre = ln2::<S>(w);
let k = round_to_nearest_int_blanket(
round_div_blanket(v_w.wrapping_mul_low_u128(one_w_pre), l2_pre),
w,
);
exp_peak_bits_model::<S>(w, k)
}
fn round_div_blanket<S: BigInt>(n: S, d: S) -> S {
let (q, r) = n.div_rem(d);
if r == S::ZERO {
return q;
}
let ar = abs(r);
let comp = abs(d) - ar;
let cmp_r = if ar < comp {
::core::cmp::Ordering::Less
} else if ar > comp {
::core::cmp::Ordering::Greater
} else {
::core::cmp::Ordering::Equal
};
let q_is_odd = q.bit(0);
let result_positive = (n < S::ZERO) == (d < S::ZERO);
if crate::support::rounding::should_bump(
RoundingMode::HalfToEven,
cmp_r,
q_is_odd,
result_positive,
) {
if result_positive { q + S::ONE } else { q - S::ONE }
} else {
q
}
}
fn round_to_nearest_int_blanket<S: BigInt>(v: S, w: u32) -> i128 {
let divisor = pow10::<S>(w);
let (q, r) = v.div_rem(divisor);
let half = divisor >> 1;
let qi = if abs(r) >= half {
if v < S::ZERO { q - S::ONE } else { q + S::ONE }
} else {
q
};
crate::int::types::traits::BigInt::to_i128(qi)
}
fn squaring_levels(w_ext: u32) -> u32 {
let p_bits = w_ext.saturating_mul(3).saturating_add(1);
let mut n: u32 = 1;
while (n + 1) * (n + 1) <= p_bits {
n += 1;
}
n
}
fn exp_peak_bits_model<S: BigInt>(w: u32, k: i128) -> u64 {
let abs_k_u128 = if k < 0 { -k } else { k } as u128;
let extra: u32 = if abs_k_u128 == 0 {
0
} else {
let digits = abs_k_u128.saturating_mul(30103).div_ceil(100_000);
let capped = digits.min((<S as BigInt>::BITS / 4) as u128) as u32;
capped + 12 + (capped >> 2)
};
let w_ext = (w + extra) as u64;
let sqr_bits = 2 * w_ext * 3322 / 1000;
let reasm_bits =
(w_ext * 3322 / 1000).saturating_add(u64::try_from(abs_k_u128).unwrap_or(u64::MAX));
let peak = if sqr_bits > reasm_bits { sqr_bits } else { reasm_bits };
peak + 64
}
#[inline]
pub(crate) fn exp_peak_fits<S: BigInt>(v_w: S, w: u32) -> bool {
exp_internal_peak_bits::<S>(v_w, w) < <S as BigInt>::BITS as u64
}
pub(crate) fn exp_fixed<S: BigInt>(v_w: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
try_exp_fixed::<S>(v_w, w)
.unwrap_or_else(|| panic!("exp_generic::exp_fixed: result out of range"))
}
pub(crate) fn try_exp_fixed<S: BigInt>(v_w: S, w: u32) -> Option<S>
where
S::Scratch: ComputeLimbs,
{
match arg_regime::<S>(v_w, w) {
ArgRegime::Overflow => return None,
ArgRegime::Underflow => return Some(lit::<S>(1)),
ArgRegime::Fits => {}
}
let one_w_pre = one::<S>(w);
let l2_pre = ln2::<S>(w);
let pow10_w_pre = one_w_pre;
let k = round_to_nearest_int(div_cached(v_w, l2_pre, pow10_w_pre), w);
if k < -1 {
let neg = (-(k + 1)) as u128;
if neg.saturating_mul(30103) >= (w as u128).saturating_mul(100_000) {
return Some(lit::<S>(1));
}
}
if k >= 0 && exp_peak_bits_model::<S>(w, k) >= <S as BigInt>::BITS as u64 {
return None;
}
let abs_k_u128 = if k < 0 { -k } else { k } as u128;
let extra: u32 = if abs_k_u128 == 0 {
0
} else {
let digits = abs_k_u128.saturating_mul(30103).div_ceil(100_000);
let capped = digits.min((<S as BigInt>::BITS / 4) as u128) as u32;
capped + 12 + (capped >> 2)
};
let extra: u32 = if k >= 0 {
extra
} else {
let w_ext_cap = ((<S as BigInt>::BITS as u64 - 4) * 1000 / 6644) as u32;
if (w as u64) + (extra as u64) <= w_ext_cap as u64 {
extra
} else {
let extra_c = w_ext_cap.saturating_sub(w);
let n_c = squaring_levels(w + extra_c) as u64;
let abs_k_u64 = abs_k_u128.min(u64::MAX as u128) as u64;
let deficit_bits = (n_c + 10).saturating_sub(abs_k_u64);
let floor_extra = (deficit_bits * 30103).div_ceil(100_000) as u32 + 1;
if extra_c < floor_extra {
return None;
}
extra_c
}
};
let w_ext = w + extra;
let v_ext = if extra == 0 {
v_w
} else {
v_w * pow10::<S>(extra)
};
let one_w = one::<S>(w_ext);
let l2 = ln2::<S>(w_ext);
let s = v_ext - scale_by_k(l2, k);
let n = squaring_levels(w_ext);
let s_red = s >> n;
let mut sum = one_w + s_red;
let mut term = s_red;
let mut iter: u128 = 2;
loop {
term = mul(term, s_red, w_ext) / lit::<S>(iter as i128);
if term == S::ZERO {
break;
}
sum = sum + term;
iter += 1;
if iter > SERIES_CAP {
break;
}
}
let mut squared = sum;
let mut i = 0;
while i < n {
squared = round_div_pow10(squared.wrapping_sqr_low_u128(), w_ext);
i += 1;
}
let sum = squared;
let scaled_at_w_ext = if k >= 0 {
let shift = k as u32;
if bit_length(sum) + shift >= <S as BigInt>::BITS {
return None;
}
sum << shift
} else {
let neg_k = -k as u128;
if neg_k >= bit_length(sum) as u128 {
return Some(lit::<S>(1));
}
sum >> (neg_k as u32)
};
let result = if extra == 0 {
scaled_at_w_ext
} else {
round_div_pow10(scaled_at_w_ext, extra)
};
if k < 0 && result == zero::<S>() {
Some(lit::<S>(1))
} else {
Some(result)
}
}
#[inline]
pub(crate) fn resize_or_panic<Src: BigInt, Dst: BigInt>(v: Src) -> Dst {
if bit_length::<Src>(abs::<Src>(v)) >= <Dst as BigInt>::BITS {
panic!("exp_generic: result out of range");
}
<Src as BigInt>::resize_to::<Dst>(v)
}
#[inline]
pub(crate) fn div<S: BigInt>(a: S, b: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
round_div(a.wrapping_mul_low_u128(pow10::<S>(w)), b)
}
pub(crate) fn sinh_pos<S: BigInt>(av_w: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
let ex = exp_fixed::<S>(av_w, w);
let enx = div(one::<S>(w), ex, w);
(ex - enx) >> 1
}
pub(crate) fn cosh_pos<S: BigInt>(av_w: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
let ex = exp_fixed::<S>(av_w, w);
let enx = div(one::<S>(w), ex, w);
(ex + enx) >> 1
}
pub(crate) fn tanh_pos<S: BigInt>(av_w: S, w: u32) -> S
where
S::Scratch: ComputeLimbs,
{
let one_w = one::<S>(w);
let thr_x = (w as i128) * 1152 / 1000 + 2;
let saturated = one_w - lit::<S>(1);
if div_rem_exact(av_w, one_w).0 > lit::<S>(thr_x) {
return saturated;
}
let m = exp_fixed::<S>(-(av_w + av_w), w);
if m == lit::<S>(0) {
return saturated;
}
div(one_w - m, one_w + m, w)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::int::types::Int;
#[test]
fn exp_fixed_k_negative_internal_peak_clamped_int24() {
let w: u32 = 184;
let v = lit::<Int<24>>(-62175) * pow10::<Int<24>>(181);
let r = try_exp_fixed::<Int<24>>(v, w)
.expect("in-range e^-62.175 at w=184 must not signal out-of-range");
assert!(
r > zero::<Int<24>>(),
"e^-62.175 must be strictly positive (a negative value is the wrap)"
);
let lo = lit::<Int<24>>(9_948_110_203_481_228_920) * pow10::<Int<24>>(138);
let hi = lit::<Int<24>>(9_948_110_203_481_228_921) * pow10::<Int<24>>(138);
assert!(
r > lo && r < hi,
"e^-62.175 · 10^184 outside its 19-digit oracle window"
);
}
#[cfg(any(feature = "d115", feature = "wide"))]
#[test]
fn exp_fixed_deep_negative_large_working_scale_int64() {
let w: u32 = 200;
let v = lit::<Int<64>>(-357) * pow10::<Int<64>>(w);
let r = exp_fixed::<Int<64>>(v, w);
assert!(r > zero::<Int<64>>(), "e^-357 must stay strictly positive");
assert!(
r > pow10::<Int<64>>(44) && r < pow10::<Int<64>>(45),
"e^-357 at working scale 200 out of its analytic bounds"
);
}
}