use crate::types::widths::D38;
pub(crate) use crate::algos::ln::fixed_d38::STRICT_GUARD;
impl<const SCALE: u32> D38<SCALE> {
#[inline]
#[must_use]
pub fn ln_strict(self) -> Self {
self.ln_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn ln_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
<Self as crate::policy::ln::LnPolicy>::ln_impl(self, mode)
}
#[inline]
#[must_use]
pub fn ln_approx(self, working_digits: u32) -> Self {
self.ln_approx_with(working_digits, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn ln_approx_with(self, working_digits: u32, mode: crate::support::rounding::RoundingMode) -> Self {
if working_digits == STRICT_GUARD {
return self.ln_strict_with(mode);
}
<Self as crate::policy::ln::LnPolicy>::ln_with_impl(self, working_digits, mode)
}
#[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 {
self.log_strict_with(base, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn log_strict_with(self, base: Self, mode: crate::support::rounding::RoundingMode) -> Self {
<Self as crate::policy::ln::LnPolicy>::log_impl(self, base, mode)
}
#[inline]
#[must_use]
pub fn log_approx(self, base: Self, working_digits: u32) -> Self {
self.log_approx_with(base, working_digits, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn log_approx_with(self, base: Self, working_digits: u32, mode: crate::support::rounding::RoundingMode) -> Self {
if working_digits == STRICT_GUARD {
return self.log_strict_with(base, mode);
}
<Self as crate::policy::ln::LnPolicy>::log_with_impl(self, base, working_digits, mode)
}
#[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 {
self.log2_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn log2_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
<Self as crate::policy::ln::LnPolicy>::log2_impl(self, mode)
}
#[inline]
#[must_use]
pub fn log2_approx(self, working_digits: u32) -> Self {
self.log2_approx_with(working_digits, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn log2_approx_with(self, working_digits: u32, mode: crate::support::rounding::RoundingMode) -> Self {
if working_digits == STRICT_GUARD {
return self.log2_strict_with(mode);
}
<Self as crate::policy::ln::LnPolicy>::log2_with_impl(self, working_digits, mode)
}
#[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 {
self.log10_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn log10_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
<Self as crate::policy::ln::LnPolicy>::log10_impl(self, mode)
}
#[inline]
#[must_use]
pub fn log10_approx(self, working_digits: u32) -> Self {
self.log10_approx_with(working_digits, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn log10_approx_with(self, working_digits: u32, mode: crate::support::rounding::RoundingMode) -> Self {
if working_digits == STRICT_GUARD {
return self.log10_strict_with(mode);
}
<Self as crate::policy::ln::LnPolicy>::log10_with_impl(self, working_digits, mode)
}
#[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 {
self.exp_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn exp_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
<Self as crate::policy::exp::ExpPolicy>::exp_impl(self, mode)
}
#[inline]
#[must_use]
pub fn exp_approx(self, working_digits: u32) -> Self {
self.exp_approx_with(working_digits, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn exp_approx_with(self, working_digits: u32, mode: crate::support::rounding::RoundingMode) -> Self {
if working_digits == STRICT_GUARD {
return self.exp_strict_with(mode);
}
<Self as crate::policy::exp::ExpPolicy>::exp_with_impl(self, working_digits, mode)
}
#[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 {
self.exp2_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn exp2_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
<Self as crate::policy::exp::ExpPolicy>::exp2_impl(self, mode)
}
#[inline]
#[must_use]
pub fn exp2_approx(self, working_digits: u32) -> Self {
self.exp2_approx_with(working_digits, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn exp2_approx_with(self, working_digits: u32, mode: crate::support::rounding::RoundingMode) -> Self {
if working_digits == STRICT_GUARD {
return self.exp2_strict_with(mode);
}
<Self as crate::policy::exp::ExpPolicy>::exp2_with_impl(self, working_digits, mode)
}
#[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::types::widths::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::types::widths::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::types::widths::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::types::widths::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::types::widths::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()
);
}
}