use crate::core_type::D38;
pub(crate) const STRICT_GUARD: u32 = 30;
pub(crate) fn wide_ln2(w: u32) -> crate::d_w128_kernels::Fixed {
crate::d_w128_kernels::Fixed::from_decimal_split(
69_314_718_055_994_530_941_723_212_145_817_u128,
65_680_755_001_343_602_552_541_206_800_094_u128,
)
.rescale_down(64, w)
}
fn wide_ln10(w: u32) -> crate::d_w128_kernels::Fixed {
crate::d_w128_kernels::Fixed::from_decimal_split(
23_025_850_929_940_456_840_179_914_546_843_u128,
64_207_601_101_488_628_772_976_033_327_901_u128,
)
.rescale_down(63, w)
}
pub(crate) fn ln_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
use crate::d_w128_kernels::Fixed;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let two_w = one_w.double();
let mut k: i32 = v_w.bit_length() as i32 - one_w.bit_length() as i32;
let m_w = loop {
let m = if k >= 0 {
v_w.shr(k as u32)
} else {
v_w.shl((-k) as u32)
};
if m.ge_mag(two_w) {
k += 1;
} else if !m.ge_mag(one_w) {
k -= 1;
} else {
break m;
}
};
let t = m_w.sub(one_w).div(m_w.add(one_w), w);
let t2 = t.mul(t, w);
let mut sum = t;
let mut term = t;
let mut j: u128 = 1;
loop {
term = term.mul(t2, w);
let contrib = term.div_small(2 * j + 1);
if contrib.is_zero() {
break;
}
sum = sum.add(contrib);
j += 1;
if j > 400 {
break;
}
}
let ln_m = sum.double();
let ln2 = wide_ln2(w);
let k_ln2 = if k >= 0 {
ln2.mul_u128(k as u128)
} else {
ln2.mul_u128((-k) as u128).neg()
};
k_ln2.add(ln_m)
}
pub(crate) fn exp_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
use crate::d_w128_kernels::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;
let mut term = one_w;
let mut n: u128 = 1;
loop {
term = term.mul(s, w).div_small(n);
if term.is_zero() {
break;
}
sum = sum.add(term);
n += 1;
if n > 400 {
break;
}
}
if k >= 0 {
let shift = k as u32;
assert!(sum.bit_length() + shift <= 256, "D38::exp: result overflows the representable range");
sum.shl(shift)
} else {
sum.shr((-k) as u32)
}
}
impl<const SCALE: u32> D38<SCALE> {
#[inline]
#[must_use]
pub fn ln_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
assert!(self.0 > 0, "D38::ln: argument must be positive");
let w = SCALE + STRICT_GUARD;
let v_w =
Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
let raw = ln_fixed(v_w, w)
.round_to_i128(w, SCALE)
.expect("D38::ln: result out of range");
Self::from_bits(raw)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn ln(self) -> Self {
self.ln_strict()
}
#[inline]
#[must_use]
pub fn log_strict(self, base: Self) -> Self {
use crate::d_w128_kernels::Fixed;
assert!(self.0 > 0, "D38::log: argument must be positive");
assert!(base.0 > 0, "D38::log: base must be positive");
let w = SCALE + STRICT_GUARD;
let pow = 10u128.pow(STRICT_GUARD);
let v_w = Fixed::from_u128_mag(self.0 as u128, false).mul_u128(pow);
let b_w = Fixed::from_u128_mag(base.0 as u128, false).mul_u128(pow);
let ln_b = ln_fixed(b_w, w);
assert!(!ln_b.is_zero(), "D38::log: base must not equal 1 (ln(1) is zero)");
let raw = ln_fixed(v_w, w)
.div(ln_b, w)
.round_to_i128(w, SCALE)
.expect("D38::log: result out of range");
Self::from_bits(raw)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn log(self, base: Self) -> Self {
self.log_strict(base)
}
#[inline]
#[must_use]
pub fn log2_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
assert!(self.0 > 0, "D38::log2: argument must be positive");
let w = SCALE + STRICT_GUARD;
let v_w =
Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
let raw = ln_fixed(v_w, w)
.div(wide_ln2(w), w)
.round_to_i128(w, SCALE)
.expect("D38::log2: result out of range");
Self::from_bits(raw)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn log2(self) -> Self {
self.log2_strict()
}
#[inline]
#[must_use]
pub fn log10_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
assert!(self.0 > 0, "D38::log10: argument must be positive");
let w = SCALE + STRICT_GUARD;
let v_w =
Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
let raw = ln_fixed(v_w, w)
.div(wide_ln10(w), w)
.round_to_i128(w, SCALE)
.expect("D38::log10: result out of range");
Self::from_bits(raw)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn log10(self) -> Self {
self.log10_strict()
}
#[inline]
#[must_use]
pub fn exp_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
if self.0 == 0 {
return Self::ONE;
}
let w = SCALE + STRICT_GUARD;
let negative_input = self.0 < 0;
let v_w = Fixed::from_u128_mag(self.0.unsigned_abs(), false)
.mul_u128(10u128.pow(STRICT_GUARD));
let v_w = if negative_input { v_w.neg() } else { v_w };
let raw = exp_fixed(v_w, w)
.round_to_i128(w, SCALE)
.expect("D38::exp: result overflows the representable range");
Self::from_bits(raw)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn exp(self) -> Self {
self.exp_strict()
}
#[inline]
#[must_use]
pub fn exp2_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
if self.0 == 0 {
return Self::ONE;
}
let w = SCALE + STRICT_GUARD;
let negative_input = self.0 < 0;
let v_w = Fixed::from_u128_mag(self.0.unsigned_abs(), 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 raw = exp_fixed(arg_w, w)
.round_to_i128(w, SCALE)
.expect("D38::exp2: result overflows the representable range");
Self::from_bits(raw)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn exp2(self) -> Self {
self.exp2_strict()
}
}
#[cfg(all(test, feature = "strict", not(feature = "fast")))]
mod strict_tests {
use crate::core_type::D38s12;
const STRICT_TOLERANCE_LSB: i128 = 2;
fn within(actual: D38s12, expected_bits: i128, tolerance: i128) -> bool {
(actual.to_bits() - expected_bits).abs() <= tolerance
}
#[test]
fn ln_of_one_is_zero() {
assert_eq!(D38s12::ONE.ln(), D38s12::ZERO);
}
#[test]
fn ln_strict_is_correctly_rounded_vs_f64() {
use crate::core_type::D38;
fn check(raw: i128) {
let x = D38::<9>::from_bits(raw);
let strict = x.ln_strict().to_bits();
let reference = {
let v = raw as f64 / 1e9;
(v.ln() * 1e9).round() as i128
};
assert!(
(strict - reference).abs() <= 1,
"ln_strict({raw}) = {strict}, f64 reference {reference}"
);
}
for &raw in &[
1,
500_000_000, 1_000_000_000, 1_500_000_000, 2_000_000_000, 2_718_281_828, 10_000_000_000, 123_456_789_012_345, 999_999_999_999_999_999, i64::MAX as i128,
] {
check(raw);
}
}
#[test]
fn strict_log_exp_family_matches_f64() {
use crate::core_type::D38;
fn check_exp(raw: i128) {
let x = D38::<9>::from_bits(raw);
let strict = x.exp_strict().to_bits();
let reference = ((raw as f64 / 1e9).exp() * 1e9).round() as i128;
assert!(
(strict - reference).abs() <= 1,
"exp_strict({raw}) = {strict}, f64 reference {reference}"
);
}
fn check_log2(raw: i128) {
let x = D38::<9>::from_bits(raw);
let strict = x.log2_strict().to_bits();
let reference = ((raw as f64 / 1e9).log2() * 1e9).round() as i128;
assert!(
(strict - reference).abs() <= 1,
"log2_strict({raw}) = {strict}, f64 reference {reference}"
);
}
fn check_log10(raw: i128) {
let x = D38::<9>::from_bits(raw);
let strict = x.log10_strict().to_bits();
let reference = ((raw as f64 / 1e9).log10() * 1e9).round() as i128;
assert!(
(strict - reference).abs() <= 1,
"log10_strict({raw}) = {strict}, f64 reference {reference}"
);
}
for &raw in &[
-5_000_000_000, -1_000_000_000, -500_000_000, 1, 500_000_000,
1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000,
] {
check_exp(raw);
}
for &raw in &[
1, 500_000_000, 1_000_000_000, 2_000_000_000, 8_000_000_000,
10_000_000_000, 123_456_789_012_345, i64::MAX as i128,
] {
check_log2(raw);
check_log10(raw);
}
}
#[test]
fn strict_exp2_at_integers() {
use crate::core_type::D38;
for k in 0_i128..=12 {
let x = D38::<12>::from_bits(k * 10i128.pow(12));
let got = x.exp2_strict().to_bits();
let expected = (1i128 << k) * 10i128.pow(12);
assert_eq!(got, expected, "2^{k}");
}
}
#[test]
fn ln_strict_of_powers_of_two() {
use crate::core_type::D38;
let ln2_s18: i128 = 693_147_180_559_945_309;
for k in 1_i128..=20 {
let x = D38::<18>::from_bits((1i128 << k) * 10i128.pow(18));
let got = x.ln_strict().to_bits();
let expected = k * ln2_s18;
let tol = k / 2 + 2;
assert!(
(got - expected).abs() <= tol,
"ln(2^{k}) = {got}, expected ≈ {expected}"
);
}
}
#[test]
fn ln_of_two_close_to_canonical() {
let two = D38s12::from_bits(2_000_000_000_000);
let result = two.ln();
assert!(
within(result, 693_147_180_560, STRICT_TOLERANCE_LSB),
"ln(2) bits = {}",
result.to_bits()
);
}
#[test]
fn ln_of_e_close_to_one() {
let e_at_s12 = D38s12::from_bits(2_718_281_828_459);
let result = e_at_s12.ln();
assert!(
within(result, 1_000_000_000_000, STRICT_TOLERANCE_LSB),
"ln(e) bits = {}, expected ~1_000_000_000_000",
result.to_bits()
);
}
#[test]
fn ln_of_ten_close_to_canonical() {
let ten = D38s12::from_bits(10_000_000_000_000);
let result = ten.ln();
assert!(
within(result, 2_302_585_092_994, STRICT_TOLERANCE_LSB),
"ln(10) bits = {}, expected ~2_302_585_092_994",
result.to_bits()
);
}
#[test]
fn ln_above_one_is_positive() {
let v = D38s12::from_bits(1_500_000_000_000); let result = v.ln();
assert!(result.to_bits() > 0);
}
#[test]
fn ln_below_one_is_negative() {
let v = D38s12::from_bits(500_000_000_000); let result = v.ln();
assert!(result.to_bits() < 0);
assert!(
within(result, -693_147_180_560, STRICT_TOLERANCE_LSB),
"ln(0.5) bits = {}, expected ~-693_147_180_560",
result.to_bits()
);
}
#[test]
#[should_panic(expected = "argument must be positive")]
fn ln_of_zero_panics() {
let _ = D38s12::ZERO.ln();
}
#[test]
#[should_panic(expected = "argument must be positive")]
fn ln_of_negative_panics() {
let neg = D38s12::from_bits(-1_000_000_000_000);
let _ = neg.ln();
}
const DERIVED_LOG_TOLERANCE_LSB: i128 = 20;
#[test]
fn log2_of_two_is_one() {
let two = D38s12::from_bits(2_000_000_000_000);
let result = two.log2();
assert!(
within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
"log2(2) bits = {}",
result.to_bits()
);
}
#[test]
fn log2_of_eight_is_three() {
let eight = D38s12::from_bits(8_000_000_000_000);
let result = eight.log2();
assert!(
within(result, 3_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
"log2(8) bits = {}",
result.to_bits()
);
}
#[test]
fn log10_of_ten_is_one() {
let ten = D38s12::from_bits(10_000_000_000_000);
let result = ten.log10();
assert!(
within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
"log10(10) bits = {}",
result.to_bits()
);
}
#[test]
fn log10_of_hundred_is_two() {
let hundred = D38s12::from_bits(100_000_000_000_000);
let result = hundred.log10();
assert!(
within(result, 2_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
"log10(100) bits = {}",
result.to_bits()
);
}
#[test]
fn log_self_is_one() {
let base = D38s12::from_bits(5_000_000_000_000); let result = base.log(base);
assert!(
within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
"log_5(5) bits = {}",
result.to_bits()
);
}
#[test]
fn log_with_base_two() {
let eight = D38s12::from_bits(8_000_000_000_000);
let two = D38s12::from_bits(2_000_000_000_000);
let result = eight.log(two);
assert!(
within(result, 3_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
"log_2(8) bits = {}",
result.to_bits()
);
}
#[test]
#[should_panic(expected = "base must not equal 1")]
fn log_base_one_panics() {
let x = D38s12::from_bits(5_000_000_000_000);
let one = D38s12::ONE;
let _ = x.log(one);
}
const EXP_TOLERANCE_LSB: i128 = 20;
#[test]
fn exp_of_zero_is_one() {
assert_eq!(D38s12::ZERO.exp(), D38s12::ONE);
}
#[test]
fn exp_of_one_is_e() {
let result = D38s12::ONE.exp();
assert!(
within(result, 2_718_281_828_459, EXP_TOLERANCE_LSB),
"exp(1) bits = {}",
result.to_bits()
);
}
#[test]
fn exp_of_ln_2_is_two() {
let ln_2 = D38s12::from_bits(693_147_180_560);
let result = ln_2.exp();
assert!(
within(result, 2_000_000_000_000, EXP_TOLERANCE_LSB),
"exp(ln 2) bits = {}",
result.to_bits()
);
}
#[test]
fn exp_of_negative_one_is_reciprocal_e() {
let neg_one = D38s12::from_bits(-1_000_000_000_000);
let result = neg_one.exp();
assert!(
within(result, 367_879_441_171, EXP_TOLERANCE_LSB),
"exp(-1) bits = {}",
result.to_bits()
);
}
#[test]
fn exp2_of_zero_is_one() {
assert_eq!(D38s12::ZERO.exp2(), D38s12::ONE);
}
#[test]
fn exp2_of_one_is_two() {
let result = D38s12::ONE.exp2();
assert!(
within(result, 2_000_000_000_000, EXP_TOLERANCE_LSB),
"exp2(1) bits = {}",
result.to_bits()
);
}
#[test]
fn exp2_of_ten_is_1024() {
let ten = D38s12::from_bits(10_000_000_000_000);
let result = ten.exp2();
assert!(
within(result, 1_024_000_000_000_000, EXP_TOLERANCE_LSB * 10),
"exp2(10) bits = {}",
result.to_bits()
);
}
}