use super::decimal_compute::{
ComputeStorage, DECIMAL_COMPUTE_DP,
decimal_compute_zero, decimal_compute_one, decimal_compute_halve,
decimal_compute_add, decimal_compute_sub, decimal_compute_mul,
decimal_compute_div, decimal_compute_div_int,
decimal_compute_is_zero, decimal_compute_is_negative,
decimal_compute_from_int, decimal_compute_cmp, decimal_compute_neg,
};
use crate::fixed_point::domains::symbolic::rational::rational_number::OverflowDetected;
thread_local! {
static LN2_CACHE: std::cell::RefCell<Option<ComputeStorage>> = const { std::cell::RefCell::new(None) };
}
fn ln2_at_compute() -> Result<ComputeStorage, OverflowDetected> {
let cached: Option<ComputeStorage> = LN2_CACHE.with(|c| c.borrow().clone());
if let Some(v) = cached {
return Ok(v);
}
let one = decimal_compute_one();
let three = decimal_compute_from_int(3);
let one_third = decimal_compute_div(one, three)?;
let s = one_third;
let s_sq = decimal_compute_mul(s, s);
let mut term = s;
let mut sum = s;
let max_terms = (DECIMAL_COMPUTE_DP as u32) + 20;
for k in 1..=max_terms {
term = decimal_compute_mul(term, s_sq);
if decimal_compute_is_zero(&term) {
break;
}
let divisor = (2 * k as u64) + 1;
let contribution = decimal_compute_div_int(term, divisor);
if decimal_compute_is_zero(&contribution) {
break;
}
sum = decimal_compute_add(sum, contribution);
}
let ln2 = decimal_compute_add(sum, sum);
LN2_CACHE.with(|c: &std::cell::RefCell<Option<ComputeStorage>>| *c.borrow_mut() = Some(ln2));
Ok(ln2)
}
pub fn decimal_ln(x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
if decimal_compute_is_negative(&x) || decimal_compute_is_zero(&x) {
return Err(OverflowDetected::DomainError);
}
let one = decimal_compute_one();
let two = decimal_compute_from_int(2);
let mut m = x;
let mut k: i64 = 0;
while decimal_compute_cmp(&m, &two) != std::cmp::Ordering::Less {
m = decimal_compute_halve(m);
k += 1;
}
while decimal_compute_cmp(&m, &one) == std::cmp::Ordering::Less {
m = decimal_compute_add(m, m);
k -= 1;
}
let m_minus_one = decimal_compute_sub(m, one);
let m_plus_one = decimal_compute_add(m, one);
let s = decimal_compute_div(m_minus_one, m_plus_one)?;
let s_sq = decimal_compute_mul(s, s);
let mut term = s;
let mut atanh_sum = s;
let max_terms = (DECIMAL_COMPUTE_DP as u32) + 20;
for j in 1..=max_terms {
term = decimal_compute_mul(term, s_sq);
if decimal_compute_is_zero(&term) {
break;
}
let divisor = (2 * j as u64) + 1;
let contribution = decimal_compute_div_int(term, divisor);
if decimal_compute_is_zero(&contribution) {
break;
}
atanh_sum = decimal_compute_add(atanh_sum, contribution);
}
let ln_m = decimal_compute_add(atanh_sum, atanh_sum);
if k == 0 {
return Ok(ln_m);
}
let ln2 = ln2_at_compute()?;
let k_ln2 = if k > 0 {
mul_compute_by_int(ln2, k as u64)
} else {
decimal_compute_neg(mul_compute_by_int(ln2, (-k) as u64))
};
Ok(decimal_compute_add(k_ln2, ln_m))
}
fn mul_compute_by_int(v: ComputeStorage, n: u64) -> ComputeStorage {
if n == 0 {
return decimal_compute_zero();
}
if n == 1 {
return v;
}
let n_compute: ComputeStorage = {
#[cfg(table_format = "q16_16")]
{ n as i64 }
#[cfg(table_format = "q32_32")]
{ n as i128 }
#[cfg(table_format = "q64_64")]
{ crate::fixed_point::i256::I256::from_i128(n as i128) }
#[cfg(table_format = "q128_128")]
{ crate::fixed_point::i512::I512::from_i128(n as i128) }
#[cfg(table_format = "q256_256")]
{ crate::fixed_point::I1024::from_i128(n as i128) }
};
v * n_compute
}
#[cfg(all(test, table_format = "q64_64"))]
mod tests {
use super::*;
use super::super::decimal_compute::pow10_compute_ct;
use crate::fixed_point::i256::I256;
fn parse_decimal_str(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");
result = result * ten + I256::from_i128(digit as i128);
}
result
}
#[test]
fn ln_one_is_zero() {
let result = decimal_ln(decimal_compute_one()).unwrap();
assert_eq!(result, decimal_compute_zero());
}
#[test]
fn ln_negative_errors() {
let neg_one = decimal_compute_neg(decimal_compute_one());
assert!(decimal_ln(neg_one).is_err());
}
#[test]
fn ln_zero_errors() {
assert!(decimal_ln(decimal_compute_zero()).is_err());
}
#[test]
fn ln_two_mpmath() {
let two = decimal_compute_from_int(2);
let result = decimal_ln(two).unwrap();
let expected = parse_decimal_str("69314718055994530941723212145817656808");
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(1_000_000);
assert!(
diff < tolerance,
"ln(2) precision: got={:?} expected={:?} diff={:?}",
result, expected, diff
);
}
#[test]
fn ln_ten_mpmath() {
let ten = decimal_compute_from_int(10);
let result = decimal_ln(ten).unwrap();
let expected = parse_decimal_str("230258509299404568401799145468436420760");
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(10_000_000);
assert!(
diff < tolerance,
"ln(10) precision: got={:?} expected={:?} diff={:?}",
result, expected, diff
);
}
#[test]
fn ln_half_mpmath() {
let half = pow10_compute_ct(37) * I256::from_i128(5);
let result = decimal_ln(half).unwrap();
let expected_abs = parse_decimal_str("69314718055994530941723212145817656808");
let result_abs = if decimal_compute_is_negative(&result) {
decimal_compute_neg(result)
} else {
result
};
let diff = if result_abs > expected_abs { result_abs - expected_abs } else { expected_abs - result_abs };
let tolerance = I256::from_i128(1_000_000);
assert!(
diff < tolerance,
"ln(0.5) precision: got={:?} expected_abs={:?}",
result, expected_abs
);
assert!(decimal_compute_is_negative(&result), "ln(0.5) should be negative");
}
}