use crate::algos::exp::exp_generic as eg;
use crate::algos::support::exp_tang_table::exp_table_entry_baked;
use crate::algos::support::wide_trig_core::WideTrigCore;
use crate::int::types::compute_limbs::ComputeLimbs;
use crate::int::types::traits::BigInt;
use crate::support::rounding::RoundingMode;
#[must_use]
pub(crate) fn tang_exp_fixed<
C: WideTrigCore,
const M: u32,
const INTERNAL_EXTRA: bool,
const SCALE: u32,
>(
v_w: C::W,
w: u32,
) -> C::W
where
<C::W as BigInt>::Scratch: ComputeLimbs,
{
tang_exp_fixed_g::<C::W, M, INTERNAL_EXTRA>(v_w, w, |ww| C::ln2::<SCALE>(ww))
}
#[must_use]
pub(crate) fn tang_exp_fixed_g<S: BigInt, const M: u32, const INTERNAL_EXTRA: bool>(
v_w: S,
w: u32,
ln2: impl Fn(u32) -> S,
) -> S
where
S::Scratch: ComputeLimbs,
{
let k = {
let one_w = eg::one::<S>(w);
eg::round_to_nearest_int::<S>(eg::div_cached::<S>(v_w, ln2(w), one_w), w)
};
let (w_ext, v_ext, extra) = if INTERNAL_EXTRA {
let extra: u32 = if k <= 0 {
0
} else {
let digits = (k as u128 * 30103).div_ceil(100_000) as u32;
digits + 12
};
let v_ext = if extra == 0 {
v_w
} else {
v_w * eg::pow10::<S>(extra)
};
(w + extra, v_ext, extra)
} else {
(w, v_w, 0)
};
{
let peak_bits =
(w_ext as u64) * 3322 / 1000 + if k >= 0 { k as u64 } else { 0 };
if peak_bits >= <S as BigInt>::BITS as u64 {
panic!("tang_exp_fixed: result out of range");
}
}
let one_w = eg::one::<S>(w_ext);
let pow10_w = one_w;
let l2 = ln2(w_ext);
let k_l2 = if k >= 0 {
l2 * eg::lit::<S>(k)
} else {
-(l2 * eg::lit::<S>(-k))
};
let s = v_ext - k_l2;
let j_signed =
eg::round_to_nearest_int::<S>(eg::div_cached::<S>(s * eg::lit::<S>(M as i128), l2, pow10_w), w_ext);
let cj_signed_w = if j_signed >= 0 {
(l2 * eg::lit::<S>(j_signed)) / eg::lit::<S>(M as i128)
} else {
-((l2 * eg::lit::<S>(-j_signed)) / eg::lit::<S>(M as i128))
};
let delta = s - cj_signed_w;
let (j_idx, k_adj) = if j_signed >= 0 {
(j_signed as u32, 0i128)
} else {
((j_signed + M as i128) as u32, -1i128)
};
debug_assert!(j_idx < M, "tang_exp_fixed: table index out of range");
let mut sum = one_w + delta;
let mut term = delta;
let mut n: u128 = 2;
loop {
term = eg::mul::<S>(term, delta, w_ext) / eg::lit::<S>(n as i128);
if term == eg::zero::<S>() {
break;
}
sum = sum + term;
n += 1;
if n > 200 {
break;
}
}
let exp_cj = exp_table_entry_baked::<S>(w_ext, j_idx as usize, M, pow10_w);
let exp_s = eg::mul::<S>(exp_cj, sum, w_ext);
let k_total = k + k_adj;
let scaled_at_w_ext = if k_total >= 0 {
let shift = k_total as u32;
if eg::bit_length::<S>(exp_s) + shift >= <S as BigInt>::BITS {
panic!("tang_exp_fixed: result out of range");
}
exp_s << shift
} else {
let neg_k = (-k_total) as u32;
if neg_k as u128 >= eg::bit_length::<S>(exp_s) as u128 {
eg::lit::<S>(1)
} else {
exp_s >> neg_k
}
};
let scaled_at_w_ext = if k_total < 0 && scaled_at_w_ext == eg::zero::<S>() {
eg::lit::<S>(1)
} else {
scaled_at_w_ext
};
if !INTERNAL_EXTRA || extra == 0 {
scaled_at_w_ext
} else {
let p = eg::pow10::<S>(extra);
let half = p / eg::lit::<S>(2);
if scaled_at_w_ext >= eg::zero::<S>() {
(scaled_at_w_ext + half) / p
} else {
-((-scaled_at_w_ext + half) / p)
}
}
}
#[inline]
#[must_use]
pub(crate) fn exp_tang<
C: WideTrigCore,
const SCALE: u32,
const M: u32,
const GUARD: u32,
const DIRECTED: bool,
const EXTERNAL_EXTRA: bool,
const INTERNAL_EXTRA: bool,
>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: ComputeLimbs,
{
if raw == C::storage_zero() {
return C::storage_one(SCALE);
}
if !DIRECTED && crate::support::rounding::is_nearest_mode(mode) {
let w = SCALE + GUARD;
let v_w = C::to_work_scaled(raw, GUARD);
let result = tang_exp_fixed::<C, M, INTERNAL_EXTRA, SCALE>(v_w, w);
return C::round_to_storage_with(result, w, SCALE, mode);
}
let base_guard = if EXTERNAL_EXTRA {
let w = SCALE + GUARD;
let one_w = C::one(w);
let v_w_probe = C::to_work_scaled(raw, GUARD);
let k = C::round_to_nearest_int(C::div_cached(v_w_probe, C::ln2::<SCALE>(w), one_w), w);
let extra: u32 = if k <= 0 {
0
} else {
let abs_k = k as u128;
let digits = (abs_k * 30103).div_ceil(100_000);
let capped = digits.min((C::w_bits() / 4) as u128) as u32;
capped + 12 + (capped >> 2)
};
GUARD + extra
} else {
GUARD
};
C::round_to_storage_directed_never_exact(base_guard, SCALE, mode, &mut |guard| {
tang_exp_fixed::<C, M, INTERNAL_EXTRA, SCALE>(C::to_work_scaled(raw, guard), SCALE + guard)
})
}
#[cfg(all(test, feature = "wide"))]
mod tests {
use crate::types::widths::{D307, D76};
use crate::RoundingMode;
const MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Trunc,
RoundingMode::Floor,
RoundingMode::Ceiling,
];
#[test]
fn tang_deep_underflow_matches_wide_series_oracle_d76_s75() {
let args = ["-20.0", "-33.5", "-34.37", "-40.0", "-45.123", "-50.25", "-55.0", "-57.5"];
for s in args {
let a76: D76<75> = s.parse().unwrap();
let a307: D307<75> = s.parse().unwrap();
for m in MODES {
let got = a76.exp_strict_with(m).to_string();
let want = a307.exp_strict_with(m).to_string();
assert_eq!(got, want, "exp({s}) D76<75> vs D307<75> oracle, mode {m:?}");
}
}
}
}
#[cfg(all(test, any(feature = "d57", feature = "d76", feature = "wide")))]
mod powf_deep_underflow_regression {
use crate::RoundingMode;
const DOWN_MODES: [RoundingMode; 5] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Trunc,
RoundingMode::Floor,
];
fn one_ulp_str(scale: usize) -> String {
let mut s = String::from("0.");
for _ in 0..scale - 1 {
s.push('0');
}
s.push('1');
s
}
#[cfg(any(feature = "d57", feature = "wide"))]
#[test]
fn powf_2_neg200_d57_mid_scales_six_modes() {
use crate::types::widths::D57;
macro_rules! check_d57 {
($s:literal) => {{
let base: D57<$s> = "2".parse().unwrap();
let exp: D57<$s> = "-200".parse().unwrap();
let zero: D57<$s> = "0".parse().unwrap();
let one_ulp: D57<$s> = one_ulp_str($s).parse().unwrap();
for m in DOWN_MODES {
assert_eq!(
base.powf_strict_with(exp, m), zero,
"D57<{}> powf(2,-200) {m:?} must round the sub-resolution positive to 0", $s
);
}
assert_eq!(
base.powf_strict_with(exp, RoundingMode::Ceiling), one_ulp,
"D57<{}> powf(2,-200) Ceiling must round the sub-resolution positive up to 1 ULP", $s
);
}};
}
check_d57!(28);
check_d57!(42);
}
#[cfg(any(feature = "d76", feature = "wide"))]
#[test]
fn powf_2_neg200_d76_mid_scales_six_modes() {
use crate::types::widths::D76;
macro_rules! check_d76 {
($s:literal) => {{
let base: D76<$s> = "2".parse().unwrap();
let exp: D76<$s> = "-200".parse().unwrap();
let zero: D76<$s> = "0".parse().unwrap();
let one_ulp: D76<$s> = one_ulp_str($s).parse().unwrap();
for m in DOWN_MODES {
assert_eq!(
base.powf_strict_with(exp, m), zero,
"D76<{}> powf(2,-200) {m:?} must round the sub-resolution positive to 0", $s
);
}
assert_eq!(
base.powf_strict_with(exp, RoundingMode::Ceiling), one_ulp,
"D76<{}> powf(2,-200) Ceiling must round the sub-resolution positive up to 1 ULP", $s
);
}};
}
check_d76!(40);
check_d76!(50);
}
}