use crate::algos::pow::powi_exact::ExactPin;
use crate::algos::support::fixed::Fixed;
use crate::algos::support::narrow_ziv::{self, WZiv};
use crate::algos::ln::ln_series_2limb::{STRICT_GUARD, wide_ln2};
use crate::int::types::Int;
use crate::support::rounding::RoundingMode;
type WNarrow = Int<24>;
#[inline]
fn exp_result_int_digits(raw: i128, scale: u32) -> u32 {
if raw <= 0 {
return 1;
}
let one_s = match 10u128.checked_pow(scale) {
Some(p) => p,
None => return 1,
};
let raw_u = raw as u128;
let q = raw_u / one_s; let r = raw_u % one_s; let q_capped = q.min(60);
let r7 = if scale <= 7 {
r * 10u128.pow(7 - scale)
} else {
r / 10u128.pow(scale - 7)
};
let x_e7 = q_capped * 10_000_000 + r7; let num = x_e7 * 434_295; (num.div_ceil(10u128.pow(13)).min(u32::MAX as u128 - 1) as u32) + 1
}
const FAST_MAX_RESULT_DIGITS: u32 = 22;
#[inline]
fn narrow_fixed_fits(raw: i128, scale: u32, w: u32) -> bool {
let _ = w;
exp_result_int_digits(raw, scale) <= FAST_MAX_RESULT_DIGITS
}
pub(crate) fn exp_fixed(v_w: Fixed, w: u32) -> Fixed {
let one_w = Fixed {
negative: false,
mag: Fixed::pow10(w),
};
if v_w.negative && w >= 6 {
let thr = Fixed {
negative: false,
mag: Fixed::pow10(w - 6),
}
.mul_u128(((w as u128) + 1) * 2_302_586);
if v_w.ge_mag(thr) {
return Fixed::ZERO;
}
}
let ln2 = wide_ln2(w);
let k = v_w.div(ln2, w).round_to_nearest_int(w);
let k_ln2 = if k >= 0 {
ln2.mul_u128(k as u128)
} else {
ln2.mul_u128((-k) as u128).neg()
};
let s = v_w.sub(k_ln2);
let p_bits = w.saturating_mul(3).saturating_add(1);
let mut n: u32 = 1;
while (n + 1) * (n + 1) <= p_bits {
n += 1;
}
let s_red = s.shr(n);
let mut sum = one_w.add(s_red);
let mut term = s_red;
let mut i: u128 = 2;
loop {
term = term.mul(s_red, w).div_small(i);
if term.is_zero() {
break;
}
sum = sum.add(term);
i += 1;
if i > 400 {
break;
}
}
for _ in 0..n {
sum = sum.mul(sum, w);
}
if k >= 0 {
let shift = u32::try_from(k).unwrap_or(u32::MAX);
assert!(
(sum.bit_length() as u64) + (shift as u64) <= 256,
"D38::exp: result out of range"
);
sum.shl(shift)
} else {
sum.shr(k.unsigned_abs().min(256) as u32)
}
}
fn exp_wide_narrow_raw(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> Option<i128> {
use crate::algos::exp::exp_generic;
let w = scale + working_digits;
let negative_input = raw < 0;
let v_mag = WNarrow::from_i128(raw.unsigned_abs() as i128) * crate::consts::pow10::dispatch::<WNarrow>(working_digits);
let v_w = if negative_input { -v_mag } else { v_mag };
let ex = exp_generic::try_exp_fixed::<WNarrow>(v_w, w)?;
narrow_round_mag(ex, working_digits, mode, true, false)
}
#[inline]
fn wnarrow_residual_clear(mag: WNarrow, shift: u32, mode: RoundingMode) -> bool {
let divisor = crate::consts::pow10::dispatch::<WNarrow>(shift);
let (_q, rem) = mag.div_rem(divisor);
let band = if shift >= 3 {
crate::consts::pow10::dispatch::<WNarrow>(shift - 3)
} else {
WNarrow::ZERO
};
let dist = if crate::support::rounding::is_nearest_mode(mode) {
let half = divisor >> 1;
if rem < half { half - rem } else { rem - half }
} else {
let comp = divisor - rem;
if rem < comp { rem } else { comp }
};
dist > band
}
fn exp_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
crate::algos::exp::exp_generic::exp_fixed::<WZiv>(narrow_ziv::lift(raw, g), scale + g)
}
fn exp_wide_narrow_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> Option<i128> {
use crate::algos::exp::exp_generic;
let w = scale + STRICT_GUARD;
let negative_input = raw < 0;
let v_mag = WNarrow::from_i128(raw.unsigned_abs() as i128)
* crate::consts::pow10::dispatch::<WNarrow>(STRICT_GUARD);
let v_w = if negative_input { -v_mag } else { v_mag };
let ex = exp_generic::try_exp_fixed::<WNarrow>(v_w, w)?;
let base = narrow_round_mag(ex, STRICT_GUARD, mode, true, false);
if wnarrow_residual_clear(ex, STRICT_GUARD, mode) {
return base;
}
narrow_ziv::walk_checked_never_exact(base, STRICT_GUARD, scale, mode, |g| {
exp_ziv(raw, scale, g)
})
}
#[inline]
fn narrow_round_mag(
mag: WNarrow,
shift: u32,
mode: RoundingMode,
never_exact: bool,
result_neg: bool,
) -> Option<i128> {
use crate::support::rounding::{is_nearest_mode, should_bump};
let divisor = crate::consts::pow10::dispatch::<WNarrow>(shift);
let (q, rem) = mag.div_rem(divisor);
let result_positive = !result_neg;
let bump = if rem != WNarrow::ZERO {
if is_nearest_mode(mode) {
let comp = divisor - rem;
let cmp_r = rem.cmp(&comp);
should_bump(mode, cmp_r, q.bit(0), result_positive)
} else {
match mode {
RoundingMode::Ceiling => result_positive,
RoundingMode::Floor => !result_positive,
_ => false, }
}
} else if never_exact {
match mode {
RoundingMode::Ceiling => result_positive,
RoundingMode::Floor => !result_positive,
_ => false,
}
} else {
false
};
let q_mag = if bump { q + WNarrow::ONE } else { q };
let bl = q_mag.bit_length();
if bl > 128 {
return None;
}
if bl == 128 {
let two_pow_127 = WNarrow::ONE << 127;
if !(result_neg && q_mag == two_pow_127) {
return None;
}
} else if bl == 127 && !result_neg {
}
let signed = if result_neg { -q_mag } else { q_mag };
Some(signed.to_i128())
}
#[inline]
fn hyper_pos_wide_narrow(av_w: WNarrow, w: u32, is_cosh: bool) -> WNarrow {
use crate::algos::exp::exp_generic;
let ex = exp_generic::exp_fixed::<WNarrow>(av_w, w);
let one_w = crate::consts::pow10::dispatch::<WNarrow>(w);
let (enx, _r) = (one_w * one_w).div_rem(ex);
let two = WNarrow::from_i128(2);
if is_cosh {
(ex + enx).div_rem(two).0
} else {
(ex - enx).div_rem(two).0
}
}
pub(crate) fn sinh_wide_narrow_raw(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
let w = scale + working_digits;
let neg = raw < 0;
let av = WNarrow::from_i128(raw.unsigned_abs() as i128) * crate::consts::pow10::dispatch::<WNarrow>(working_digits);
let sh = hyper_pos_wide_narrow(av, w, false);
narrow_round_mag(sh, working_digits, mode, true, neg).unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::sinh", scale)
})
}
pub(crate) fn cosh_wide_narrow_raw(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
let w = scale + working_digits;
let av = WNarrow::from_i128(raw.unsigned_abs() as i128) * crate::consts::pow10::dispatch::<WNarrow>(working_digits);
let ch = hyper_pos_wide_narrow(av, w, true);
narrow_round_mag(ch, working_digits, mode, true, false).unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::cosh", scale)
})
}
#[inline]
pub(crate) fn hyper_needs_wide_narrow(raw: i128, scale: u32, w: u32) -> bool {
!narrow_fixed_fits(raw.unsigned_abs() as i128, scale, w)
}
#[inline]
#[must_use]
pub(crate) fn exp_with(
raw: Int<2>,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> Option<Int<2>> {
exp_with_raw(raw.as_i128(), scale, working_digits, mode).map(Int::<2>::from_i128)
}
#[inline]
fn exp_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<i128> {
if raw == 0 {
return Some(10_i128.pow(scale)); }
let w = scale + working_digits;
if !narrow_fixed_fits(raw, scale, w) || !crate::support::rounding::is_nearest_mode(mode) {
return exp_wide_narrow_raw(raw, scale, working_digits, mode);
}
let negative_input = raw < 0;
let v_w = Fixed::from_u128_mag(raw.unsigned_abs(), false).mul_u128(10u128.pow(working_digits));
let v_w = if negative_input { v_w.neg() } else { v_w };
exp_fixed(v_w, w).round_to_i128_with(w, scale, mode)
}
#[inline]
#[must_use]
pub(crate) fn exp_strict<const SCALE: u32>(raw: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
exp_strict_raw::<SCALE>(raw.as_i128(), mode).map(Int::<2>::from_i128)
}
#[inline]
fn exp_strict_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> Option<i128> {
if raw == 0 {
return Some(10_i128.pow(SCALE));
}
let w = SCALE + STRICT_GUARD;
if !narrow_fixed_fits(raw, SCALE, w) || !crate::support::rounding::is_nearest_mode(mode) {
return exp_wide_narrow_strict_raw(raw, SCALE, mode);
}
let negative_input = raw < 0;
let v_w = Fixed::from_u128_mag(raw.unsigned_abs(), false).mul_u128(10u128.pow(STRICT_GUARD));
let v_w = if negative_input { v_w.neg() } else { v_w };
let v = exp_fixed(v_w, w);
match v.round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(r) => r,
None => narrow_ziv::walk_checked_never_exact(
v.round_to_i128_with(w, SCALE, mode),
STRICT_GUARD,
SCALE,
mode,
|g| exp_ziv(raw, SCALE, g),
),
}
}
#[inline]
fn exp2_exact_pin(raw: i128, scale: u32, mode: RoundingMode) -> ExactPin<i128> {
let one_s = match 10i128.checked_pow(scale) {
Some(v) => v,
None => return ExactPin::Defer,
};
if raw % one_s != 0 {
return ExactPin::Defer;
}
let k = raw / one_s;
if k == 0 {
return ExactPin::Value(one_s);
}
let kk = k.unsigned_abs();
if k > 0 {
let mut v: i128 = one_s;
for _ in 0..kk {
v = match v.checked_mul(2) {
Some(d) => d,
None => return ExactPin::OutOfRange,
};
}
ExactPin::Value(v)
} else if kk <= scale as u128 {
let mut v = match 10i128.checked_pow(scale - kk as u32) {
Some(d) => d,
None => return ExactPin::Defer,
};
for _ in 0..kk {
v = match v.checked_mul(5) {
Some(d) => d,
None => return ExactPin::Defer,
};
}
ExactPin::Value(v)
} else {
let num = match 5u128.checked_pow(scale) {
Some(d) => d, None => return ExactPin::Defer,
};
let p = kk as u32 - scale; ExactPin::Value(round_pow2_fraction(num, p, mode))
}
}
#[inline]
fn round_pow2_fraction(num: u128, p: u32, mode: RoundingMode) -> i128 {
if p >= 128 {
let bump = crate::support::rounding::should_bump(
mode,
::core::cmp::Ordering::Less, false, true, );
return i128::from(bump);
}
let q = (num >> p) as i128;
let r = num & ((1u128 << p) - 1);
if r == 0 {
return q;
}
let half = 1u128 << (p - 1);
let cmp_r = r.cmp(&half);
let q_is_odd = (q & 1) == 1;
let bump = crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, true);
q + i128::from(bump)
}
#[inline]
#[must_use]
pub(crate) fn exp2_with(
raw: Int<2>,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> Option<Int<2>> {
exp2_with_raw(raw.as_i128(), scale, working_digits, mode).map(Int::<2>::from_i128)
}
#[inline]
fn exp2_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<i128> {
if raw == 0 {
return Some(10_i128.pow(scale));
}
match exp2_exact_pin(raw, scale, mode) {
ExactPin::Value(pinned) => return Some(pinned),
ExactPin::OutOfRange => return None,
ExactPin::Defer => {}
}
let abs_raw = raw.unsigned_abs();
if exp2_result_int_digits(abs_raw, scale) > FAST_MAX_RESULT_DIGITS {
return exp2_wide_narrow_raw(raw, scale, working_digits, mode);
}
let w = scale + working_digits;
let negative_input = raw < 0;
let v_w = Fixed::from_u128_mag(raw.unsigned_abs(), false).mul_u128(10u128.pow(working_digits));
let v_w = if negative_input { v_w.neg() } else { v_w };
let arg_w = v_w.mul(wide_ln2(w), w);
exp_fixed(arg_w, w).round_to_i128_with(w, scale, mode)
}
#[inline]
fn exp2_result_int_digits(abs_raw: u128, scale: u32) -> u32 {
let one_s = match 10u128.checked_pow(scale) {
Some(p) => p,
None => return 1,
};
let q = abs_raw / one_s; let r = abs_raw % one_s; let q_capped = q.min(180); let r7 = if scale <= 7 {
r * 10u128.pow(7 - scale)
} else {
r / 10u128.pow(scale - 7)
};
let x_e7 = q_capped * 10_000_000 + r7; let num = x_e7 * 301_030; (num.div_ceil(10u128.pow(13)).min(u32::MAX as u128 - 1) as u32) + 1
}
#[inline]
fn exp2_result_int_digits_floor(abs_raw: u128, scale: u32) -> u32 {
let one_s = match 10u128.checked_pow(scale) {
Some(p) => p,
None => return 1,
};
let q = abs_raw / one_s; let r = abs_raw % one_s; let q_capped = q.min(180);
let r7 = if scale <= 7 {
r * 10u128.pow(7 - scale)
} else {
r / 10u128.pow(scale - 7)
};
let x_e7 = q_capped * 10_000_000 + r7; let num = x_e7 * 301_029; ((num / 10u128.pow(13)).min(u32::MAX as u128 - 1) as u32) + 1
}
fn exp2_wide_narrow_raw(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> Option<i128> {
let (ex, guard) = exp2_wide_narrow_eval(raw, scale, working_digits)?;
narrow_round_mag(ex, guard, mode, true, false)
}
fn exp2_wide_narrow_eval(
raw: i128,
scale: u32,
working_digits: u32,
) -> Option<(WNarrow, u32)> {
use crate::algos::exp::exp_generic;
let neg = raw < 0;
let abs_raw = raw.unsigned_abs();
if raw > 0 && exp2_result_int_digits_floor(abs_raw, scale).saturating_add(scale) >= 40 {
return None;
}
let k_lift = if neg { 0 } else { exp2_result_int_digits(abs_raw, scale) };
let guard = working_digits + k_lift;
let w = scale + guard;
let x_w =
WNarrow::from_i128(abs_raw as i128) * crate::consts::pow10::dispatch::<WNarrow>(guard);
let ln2_w = crate::consts::ln2_by_working_scale::<WNarrow>(w, RoundingMode::HalfToEven);
let prod = x_w * ln2_w;
let arg_mag = if w <= 38 {
crate::algos::support::mg_divide::div_wide_pow10::<WNarrow>(prod, w, RoundingMode::HalfToEven)
} else {
crate::algos::support::mg_divide::div_wide_pow10_chain::<WNarrow>(
prod,
w,
RoundingMode::HalfToEven,
)
};
let arg = if neg { -arg_mag } else { arg_mag };
let ex = exp_generic::try_exp_fixed::<WNarrow>(arg, w)?;
Some((ex, guard))
}
fn exp2_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
use crate::algos::exp::exp_generic as eg;
let w = scale + g;
let arg = eg::mul::<WZiv>(narrow_ziv::lift(raw, g), narrow_ziv::ln2_w(w), w);
eg::exp_fixed::<WZiv>(arg, w)
}
#[inline]
#[must_use]
pub(crate) fn exp2_strict<const SCALE: u32>(raw: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
exp2_strict_raw(raw.as_i128(), SCALE, mode).map(Int::<2>::from_i128)
}
fn exp2_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> Option<i128> {
if raw == 0 {
return Some(10_i128.pow(scale));
}
match exp2_exact_pin(raw, scale, mode) {
ExactPin::Value(pinned) => return Some(pinned),
ExactPin::OutOfRange => return None,
ExactPin::Defer => {}
}
let abs_raw = raw.unsigned_abs();
if exp2_result_int_digits(abs_raw, scale) > FAST_MAX_RESULT_DIGITS {
let (ex, guard) = exp2_wide_narrow_eval(raw, scale, STRICT_GUARD)?;
let base = narrow_round_mag(ex, guard, mode, true, false);
if wnarrow_residual_clear(ex, guard, mode) {
return base;
}
return narrow_ziv::walk_checked_never_exact(base, STRICT_GUARD, scale, mode, |g| {
exp2_ziv(raw, scale, g)
});
}
let w = scale + STRICT_GUARD;
let negative_input = raw < 0;
let v_w = Fixed::from_u128_mag(abs_raw, false).mul_u128(10u128.pow(STRICT_GUARD));
let v_w = if negative_input { v_w.neg() } else { v_w };
let arg_w = v_w.mul(wide_ln2(w), w);
let v = exp_fixed(arg_w, w);
match v.round_to_i128_clear_of_tie(w, scale, mode) {
Some(r) => r,
None => narrow_ziv::walk_checked_never_exact(
v.round_to_i128_with(w, scale, mode),
STRICT_GUARD,
scale,
mode,
|g| exp2_ziv(raw, scale, g),
),
}
}
#[cfg(test)]
mod deep_underflow_directed {
use super::*;
const ALL_MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Ceiling,
RoundingMode::Floor,
RoundingMode::Trunc,
];
fn raw_at(scale: u32) -> i128 {
-6_217_530_480_440_519 * 10_i128.pow(scale - 14)
}
#[test]
fn exp_deep_underflow_rounds_correctly_all_modes() {
let cells: [(u32, fn(RoundingMode) -> Option<i128>); 3] = [
(17, |m| exp_strict_raw::<17>(raw_at(17), m)),
(18, |m| exp_strict_raw::<18>(raw_at(18), m)),
(19, |m| exp_strict_raw::<19>(raw_at(19), m)),
];
for (scale, run) in cells {
for mode in ALL_MODES {
let want = if mode == RoundingMode::Ceiling { 1 } else { 0 };
assert_eq!(
run(mode),
Some(want),
"exp(-62.17530480440519) scale={scale} mode={mode:?}"
);
}
}
}
}
#[cfg(test)]
mod fast_path_validity {
use super::*;
fn fast_exp_raw_ungated(raw: i128, scale: u32, mode: RoundingMode) -> Option<i128> {
if raw == 0 {
return Some(10_i128.pow(scale));
}
let w = scale + STRICT_GUARD;
let negative_input = raw < 0;
let v_w =
Fixed::from_u128_mag(raw.unsigned_abs(), false).mul_u128(10u128.pow(STRICT_GUARD));
let v_w = if negative_input { v_w.neg() } else { v_w };
std::panic::catch_unwind(|| exp_fixed(v_w, w).round_to_i128_with(w, scale, mode))
.unwrap_or(None)
}
const MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Ceiling,
RoundingMode::Floor,
RoundingMode::Trunc,
];
fn gate_keeps_fast(raw: i128, scale: u32, mode: RoundingMode) -> bool {
let w = scale + STRICT_GUARD;
narrow_fixed_fits(raw, scale, w) && crate::support::rounding::is_nearest_mode(mode)
}
#[test]
fn fast_path_bit_identical_to_wide_d38() {
std::panic::set_hook(Box::new(|_| {}));
let mut checked = 0u64;
for scale in 0u32..=38 {
let one_s = 10f64.powi(scale as i32);
let mut x10 = 1u64;
while x10 <= 1000 {
let x = x10 as f64 / 10.0;
for sign in [1i128, -1] {
let raw_f = sign as f64 * x * one_s;
if raw_f.abs() >= (i128::MAX as f64) {
x10 += 1;
continue;
}
let raw = raw_f as i128;
if raw == 0 {
continue;
}
for mode in MODES {
if !gate_keeps_fast(raw, scale, mode) {
continue; }
let wide = match std::panic::catch_unwind(|| {
exp_wide_narrow_raw(raw, scale, STRICT_GUARD, mode)
}) {
Ok(Some(v)) => v,
Ok(None) | Err(_) => continue,
};
let fast = fast_exp_raw_ungated(raw, scale, mode);
assert_eq!(
fast,
Some(wide),
"fast != wide at scale={scale} raw={raw} mode={mode:?} (gate kept fast)"
);
checked += 1;
}
}
x10 += 1;
}
}
assert!(checked > 100_000, "too few cells checked: {checked}");
}
#[test]
fn wide_only_cells_are_routed_wide() {
use RoundingMode::*;
for &raw in &[66i128, 85, 100] {
assert!(
!gate_keeps_fast(raw, 0, HalfToEven),
"exp({raw}) s0 should route WIDE (integer regime)"
);
}
for mode in [Ceiling, Floor, Trunc] {
assert!(
!gate_keeps_fast(-1, 37, mode),
"exp(-1e-37) s37 {mode:?} should route WIDE (directed)"
);
assert!(
!gate_keeps_fast(2 * 10i128.pow(0), 0, mode),
"exp(2) s0 {mode:?} should route WIDE (directed)"
);
}
assert!(
gate_keeps_fast(15 * 10i128.pow(18), 19, HalfToEven),
"exp(1.5) D38 s19 HalfToEven should stay FAST (common cell)"
);
assert!(
gate_keeps_fast(-1, 37, HalfToEven),
"exp(-1e-37) s37 HalfToEven should stay FAST"
);
}
#[test]
fn exp2_integer_regime_matches_golden_floor() {
const S9: u32 = 9;
let raw = 93_013_986_656_i128; let gfloor: i128 = 9_999_999_994_134_964_658_924_521_484_307_802_708;
for &mode in &MODES {
let got = exp2_with_raw(raw, S9, STRICT_GUARD, mode);
let want = if matches!(mode, RoundingMode::Ceiling) {
gfloor + 1
} else {
gfloor
};
assert_eq!(got, Some(want), "exp2(93.013986656) s9 mode={mode:?}");
}
for &r in &[50_000_000_000_i128, 70_000_000_000, 90_000_000_000] {
let he = exp2_with_raw(r, S9, STRICT_GUARD, RoundingMode::HalfToEven).expect("in range");
let fl = exp2_with_raw(r, S9, STRICT_GUARD, RoundingMode::Floor).expect("in range");
let ce = exp2_with_raw(r, S9, STRICT_GUARD, RoundingMode::Ceiling).expect("in range");
assert!(fl <= he && he <= ce, "exp2 rounding order violated at raw={r}");
assert!(ce - fl <= 1, "exp2 floor/ceil differ by >1 at raw={r}");
}
}
}