use crate::algos::ln::ln_series_2limb::wide_ln2;
use crate::algos::support::fixed::Fixed;
use crate::algos::support::wide_trig_core::WideTrigCore;
use crate::int::types::Int;
use crate::support::rounding::RoundingMode;
pub(crate) const SCHOOLBOOK_GUARD: u32 = 30;
pub(crate) fn exp_schoolbook_fixed(v_w: Fixed, w: u32) -> Fixed {
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
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 mut sum = one_w.add(s); let mut term = s; let mut i: u128 = 2;
loop {
term = term.mul(s, w).div_small(i);
if term.is_zero() {
break;
}
sum = sum.add(term);
i += 1;
if i > 300 {
break;
}
}
if k >= 0 {
let shift = k as u32;
assert!(
sum.bit_length() + shift <= 256,
"exp_schoolbook: result overflows the representable range"
);
sum.shl(shift)
} else {
sum.shr((-k) as u32)
}
}
#[inline]
#[must_use]
pub(crate) fn exp_schoolbook<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage {
if raw == C::storage_zero() {
return C::storage_one(SCALE);
}
C::round_to_storage_directed_never_exact(C::GUARD, SCALE, mode, &mut |guard| {
C::exp_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard)
})
}
#[allow(dead_code)]
pub(crate) fn exp_schoolbook_with(
raw: Int<2>,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> Int<2> {
let raw_i = raw.as_i128();
if raw_i == 0 {
return Int::<2>::from_i128(10_i128.pow(scale));
}
let w = scale + working_digits;
let negative_input = raw_i < 0;
let v_w = Fixed::from_u128_mag(raw_i.unsigned_abs(), false)
.mul_u128(10u128.pow(working_digits));
let v_w = if negative_input { v_w.neg() } else { v_w };
Int::<2>::from_i128(
exp_schoolbook_fixed(v_w, w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale(
"exp_schoolbook",
scale,
)
}),
)
}
#[allow(dead_code)]
pub(crate) fn exp_schoolbook_strict<const SCALE: u32>(
raw: Int<2>,
mode: RoundingMode,
) -> Int<2> {
exp_schoolbook_with(raw, SCALE, SCHOOLBOOK_GUARD, mode)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algos::exp::exp_series_2limb::exp_strict;
use crate::support::rounding::RoundingMode;
use crate::int::types::Int;
const MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven, RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero, RoundingMode::Trunc,
RoundingMode::Floor, RoundingMode::Ceiling,
];
#[track_caller]
fn check<const S: u32>(raw_i: i128, mode: RoundingMode) {
let raw = Int::<2>::from_i128(raw_i);
let got = exp_schoolbook_strict::<S>(raw, mode);
let expected = exp_strict::<S>(raw, mode).expect("reference in range");
assert_eq!(got, expected,
"exp schoolbook D38<{}> raw={} mode={:?}: {:?} != {:?}",
S, raw_i, mode, got, expected);
}
#[test]
fn exp_schoolbook_matches_series_d38_s12() {
for raw_i in [0_i128, 500_000_000_000, 1_000_000_000_000,
-500_000_000_000, 2_000_000_000_000, 693_147_180_560,
-1_000_000_000_000, 3_000_000_000_000, 100_000_000_000] {
for mode in MODES { check::<12>(raw_i, mode); }
}
}
#[test]
fn exp_schoolbook_matches_series_d38_s19() {
let one: i128 = 10_i128.pow(19);
for raw_i in [0, one / 2, one, -(one / 2), 2 * one, -one,
one * 693_147_180 / 1_000_000_000] {
for mode in MODES { check::<19>(raw_i, mode); }
}
}
#[cfg(any(feature = "d57", feature = "wide"))]
mod wide_d57 {
use super::*;
use crate::types::widths::wide_trig_d57::Core;
use crate::D;
const S: u32 = 19;
fn raw9(units: i128) -> Int<3> {
Int::<3>::from_i128(units * 10_i128.pow(10))
}
const INPUTS9: [i128; 7] = [
0, 500_000_000, 1_000_000_000, -500_000_000,
2_000_000_000, -1_000_000_000, 693_147_180,
];
#[test]
fn exp_schoolbook_matches_routed() {
for &u in &INPUTS9 {
let r = raw9(u);
for mode in MODES {
assert_eq!(
crate::algos::exp::exp_schoolbook::exp_schoolbook::<Core, S>(r, mode),
D::<Int<3>, S>(r).exp_strict_with(mode).0,
"D57 exp schoolbook != routed at units={u} mode={mode:?}"
);
}
}
}
}
}