use super::decimal_compute::{
ComputeStorage, DECIMAL_COMPUTE_DP,
decimal_compute_zero, decimal_compute_one, decimal_compute_from_int,
decimal_compute_is_zero, decimal_compute_is_negative, decimal_compute_cmp,
decimal_compute_neg, try_decimal_compute_neg,
};
use crate::fixed_point::domains::symbolic::rational::rational_number::OverflowDetected;
use std::cell::RefCell;
use super::hp::*;
struct HpConsts {
one: Hp,
half: Hp,
ln2_hi: Hp,
ln2_lo: Hp,
ln2: Hp,
ln2_15: i128,
table: [[Hp; 10]; 4],
}
thread_local! {
static HP_CONSTS: RefCell<Option<std::rc::Rc<HpConsts>>> = const { RefCell::new(None) };
}
fn hp_consts() -> std::rc::Rc<HpConsts> {
if let Some(c) = HP_CONSTS.with(|c| c.borrow().clone()) {
return c;
}
let c = std::rc::Rc::new(build_hp_consts());
HP_CONSTS.with(|slot| *slot.borrow_mut() = Some(c.clone()));
c
}
fn hp_exp_taylor(y: Hp, one: Hp, half: Hp) -> Hp {
let mut term = one;
let mut sum = one;
for k in 1..=(HP_DP as u64 + 10) {
term = hp_div_small_round(hp_mul(term, y, half), k);
if hp_is_zero(&term) {
break;
}
sum = sum + term;
}
sum
}
fn build_hp_consts() -> HpConsts {
let one = hp_pow10(HP_DP);
let half = hp_divmod_small(one, 2).0;
let mut p = hp_divmod_small(hp_mul_small(hp_pow10(2 * HP_DP), 2), 3).0;
let mut sum = p;
let mut k: u64 = 1;
loop {
p = hp_divmod_small(p, 9).0;
if hp_is_zero(&p) {
break;
}
sum = sum + hp_divmod_small(p, 2 * k + 1).0;
k += 1;
}
let ln2_hi = hp_div_pow10(sum, HP_DP);
let ln2_lo = sum - hp_mul_pow10(ln2_hi, HP_DP);
let ln2 = ln2_hi + hp_div_pow10(ln2_lo + half, HP_DP);
let ln2_15 = hp_to_i128(&hp_div_pow10(ln2_hi, HP_DP - 15));
let mut table = [[one; 10]; 4];
for (s, row) in table.iter_mut().enumerate() {
let base = hp_exp_taylor(hp_pow10(HP_DP - 1 - s as u32), one, half);
for d in 1..10 {
row[d] = hp_mul(row[d - 1], base, half);
}
}
HpConsts { one, half, ln2_hi, ln2_lo, ln2, ln2_15, table }
}
fn n_ln2(c: &HpConsts, n: i64) -> Hp {
let m = n.unsigned_abs();
let v = hp_mul_small(c.ln2_hi, m) + hp_div_pow10(hp_mul_small(c.ln2_lo, m) + c.half, HP_DP);
if n < 0 { -v } else { v }
}
fn exp_hp(x: ComputeStorage) -> Result<Option<(Hp, i64)>, OverflowDetected> {
let bound = decimal_compute_from_int(COMPUTE_BITS);
if decimal_compute_cmp(&x, &bound) == std::cmp::Ordering::Greater {
return Err(OverflowDetected::TierOverflow);
}
if decimal_compute_cmp(&x, &decimal_compute_neg(bound)) == std::cmp::Ordering::Less {
return Ok(None);
}
let c = hp_consts();
let negative = decimal_compute_is_negative(&x);
let magnitude = hp_mul_pow10(
compute_to_hp(if negative { decimal_compute_neg(x) } else { x }),
HP_DP - DECIMAL_COMPUTE_DP as u32,
);
let xh = if negative { -magnitude } else { magnitude };
let x15 = hp_to_i128(&hp_div_pow10(magnitude, HP_DP - 15));
let est = (x15 / c.ln2_15) as i64;
let mut n = if negative { -est - 1 } else { est };
let mut stepped_down = false;
let r = loop {
let r = xh - n_ln2(&c, n);
if hp_is_negative(&r) {
n -= 1;
stepped_down = true;
} else if r >= c.ln2 && !stepped_down {
n += 1;
} else {
break r;
}
};
let q = hp_to_i128(&hp_div_pow10(r, HP_DP - 4)) as usize;
let s = r - hp_mul_pow10(hp_small(q as u64), HP_DP - 4);
let digits = [q / 1000, (q / 100) % 10, (q / 10) % 10, q % 10];
let mut m = c.one;
for (stage, &d) in digits.iter().enumerate() {
if d != 0 {
m = hp_mul(m, c.table[stage][d], c.half);
}
}
if !hp_is_zero(&s) {
m = hp_mul(m, hp_exp_taylor(s, c.one, c.half), c.half);
}
Ok(Some((m, n)))
}
fn hp_scaled_to_compute(m: Hp, n: i64) -> Result<ComputeStorage, OverflowDetected> {
let e = HP_DP - DECIMAL_COMPUTE_DP as u32;
let q = if n >= 0 {
if hp_bit_length(&m) as i64 + n > HP_BITS as i64 - 2 {
return Err(OverflowDetected::TierOverflow);
}
hp_div_round_half_even(hp_shl(m, n as u32), e, 0)
} else {
let k = n.unsigned_abs();
if hp_bit_length(&hp_pow10(e)) as u64 + k > HP_BITS as u64 - 2 {
return Ok(decimal_compute_zero());
}
hp_div_round_half_even(m, e, k as u32)
};
hp_to_compute(&q)
}
pub fn decimal_exp(x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
if decimal_compute_is_zero(&x) {
return Ok(decimal_compute_one());
}
match exp_hp(x)? {
None => Ok(decimal_compute_zero()),
Some((m, n)) => hp_scaled_to_compute(m, n),
}
}
#[allow(dead_code)]
pub fn decimal_exp_neg(x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
decimal_exp(decimal_compute_neg(x))
}
pub fn decimal_sinhcosh(x: ComputeStorage) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
if decimal_compute_is_zero(&x) {
return Ok((decimal_compute_zero(), decimal_compute_one()));
}
let negative = decimal_compute_is_negative(&x);
let a = if negative { try_decimal_compute_neg(x)? } else { x };
let (m1, n1) = exp_hp(a)?.ok_or(OverflowDetected::TierOverflow)?;
if hp_bit_length(&m1) as i64 + n1 > HP_BITS as i64 - 2 {
return Err(OverflowDetected::TierOverflow);
}
let ep = hp_shl(m1, n1 as u32);
let en = match exp_hp(decimal_compute_neg(a))? {
None => hp_small(0),
Some((m2, n2)) if n2.unsigned_abs() < HP_BITS as u64 => hp_shr(m2, n2.unsigned_abs() as u32),
Some(_) => hp_small(0),
};
let e = HP_DP - DECIMAL_COMPUTE_DP as u32;
let sinh = hp_to_compute(&hp_div_round_half_even(ep - en, e, 1))?;
let cosh = hp_to_compute(&hp_div_round_half_even(ep + en, e, 1))?;
Ok((if negative { decimal_compute_neg(sinh) } else { sinh }, cosh))
}
#[cfg(all(test, table_format = "q64_64"))]
mod tests {
use super::*;
use super::super::decimal_compute::{decimal_compute_from_int, pow10_compute_ct};
use crate::fixed_point::i256::I256;
#[test]
fn exp_zero_is_one() {
let result = decimal_exp(decimal_compute_zero()).unwrap();
assert_eq!(result, decimal_compute_one());
}
#[test]
fn exp_one_within_1_ulp() {
let result = decimal_exp(decimal_compute_one()).unwrap();
let expected_str = "271828182845904523536028747135266249776";
let expected = parse_decimal_str_q64_64(expected_str);
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(10_000);
assert!(
diff < tolerance,
"exp(1) precision check: got={:?}, expected={:?}, diff={:?}",
result, expected, diff
);
}
#[test]
fn exp_half_within_1_ulp() {
let half = pow10_compute_ct(37) * I256::from_i128(5);
let result = decimal_exp(half).unwrap();
let expected_str = "164872127070012814684865078781416357165";
let expected = parse_decimal_str_q64_64(expected_str);
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(10_000);
assert!(
diff < tolerance,
"exp(0.5) precision check: got={:?}, expected={:?}, diff={:?}",
result, expected, diff
);
}
fn parse_decimal_str_q64_64(s: &str) -> I256 {
let mut result = I256::from_i128(0);
let ten = I256::from_i128(10);
for ch in s.chars() {
let digit = ch.to_digit(10).expect("non-digit in test string");
result = result * ten + I256::from_i128(digit as i128);
}
result
}
#[test]
fn exp_two_within_reasonable() {
let two = decimal_compute_from_int(2);
let result = decimal_exp(two).unwrap();
let expected_str = "738905609893065022723042746057500781318";
let expected = parse_decimal_str_q64_64(expected_str);
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(100_000);
assert!(
diff < tolerance,
"exp(2) precision check: got={:?}, expected={:?}, diff={:?}",
result, expected, diff
);
}
#[test]
fn exp_neg_one() {
use crate::canonical::{gmath, evaluate};
let result = evaluate(&gmath("-1.0").exp()).unwrap();
let s = format!("{}", result);
assert!(s.starts_with("0.3678794411714"),
"exp(-1) at storage tier should match mpmath to 13+ digits, got: {}", s);
}
}