use crate::algos::fixed_d38::Fixed;
use crate::support::rounding::RoundingMode;
pub(crate) const STRICT_GUARD: u32 = 30;
const LN2_S75: &str =
"693147180559945309417232121458176568075500134360255254120680009493393621969";
const LN10_S75: &str =
"2302585092994045684017991454684364207601101488628772976033327900967572609677";
const LN2_RAW: crate::wide_int::Int256 =
match crate::wide_int::Int256::from_str_radix(LN2_S75, 10) {
Ok(v) => v,
Err(_) => panic!("algos::ln::fixed_d38: LN2_S75 not parseable"),
};
const LN10_RAW: crate::wide_int::Int256 =
match crate::wide_int::Int256::from_str_radix(LN10_S75, 10) {
Ok(v) => v,
Err(_) => panic!("algos::ln::fixed_d38: LN10_S75 not parseable"),
};
#[inline]
fn fixed_from_int256(raw: crate::wide_int::Int256) -> Fixed {
let words = raw.0;
Fixed {
negative: false,
mag: [
(words[0] as u128) | ((words[1] as u128) << 64),
(words[2] as u128) | ((words[3] as u128) << 64),
],
}
}
pub(crate) fn wide_ln2(w: u32) -> Fixed {
debug_assert!(w <= 75, "wide_ln2: working scale {w} exceeds embedded 75-digit ln 2");
let ln2_at_75 = fixed_from_int256(LN2_RAW);
if w == 75 { ln2_at_75 } else { ln2_at_75.rescale_down(75, w) }
}
pub(crate) fn wide_ln10(w: u32) -> Fixed {
debug_assert!(w <= 75, "wide_ln10: working scale {w} exceeds embedded 75-digit ln 10");
let ln10_at_75 = fixed_from_int256(LN10_RAW);
if w == 75 { ln10_at_75 } else { ln10_at_75.rescale_down(75, w) }
}
pub(crate) fn ln_fixed(v_w: Fixed, w: u32) -> 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)
}
#[inline]
#[must_use]
pub(crate) fn ln_with(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> i128 {
assert!(raw > 0, "ln kernel: argument must be positive");
let one_bits: i128 = 10_i128.pow(scale);
if raw == one_bits {
return 0;
}
let delta = raw - one_bits;
let ln1p_band: i128 = 10_i128.pow(scale.saturating_sub((scale + 1) / 2));
if delta.abs() <= ln1p_band {
return delta;
}
let w = scale + working_digits;
let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(working_digits));
ln_fixed(v_w, w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("ln kernel", scale))
}
#[inline]
#[must_use]
pub(crate) fn ln_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
assert!(raw > 0, "ln kernel: argument must be positive");
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return 0;
}
let delta = raw - one_bits;
let ln1p_band: i128 = 10_i128.pow(SCALE.saturating_sub((SCALE + 1) / 2));
if delta.abs() <= ln1p_band {
return delta;
}
let w = SCALE + STRICT_GUARD;
let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
ln_fixed(v_w, w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("ln kernel", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn log_with(
raw: i128,
base_raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
assert!(raw > 0, "D38::log: argument must be positive");
assert!(base_raw > 0, "D38::log: base must be positive");
let w = scale + working_digits;
let pow = 10u128.pow(working_digits);
let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(pow);
let b_w = Fixed::from_u128_mag(base_raw 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)");
ln_fixed(v_w, w)
.div(ln_b, w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::log", scale))
}
#[inline]
#[must_use]
pub(crate) fn log_strict<const SCALE: u32>(
raw: i128,
base_raw: i128,
mode: RoundingMode,
) -> i128 {
log_with(raw, base_raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn log2_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
assert!(raw > 0, "D38::log2: argument must be positive");
let w = scale + working_digits;
let v_w =
Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(working_digits));
ln_fixed(v_w, w)
.div(wide_ln2(w), w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::log2", scale))
}
#[inline]
#[must_use]
pub(crate) fn log2_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
log2_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn log10_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
assert!(raw > 0, "D38::log10: argument must be positive");
let w = scale + working_digits;
let v_w =
Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(working_digits));
ln_fixed(v_w, w)
.div(wide_ln10(w), w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::log10", scale))
}
#[inline]
#[must_use]
pub(crate) fn log10_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
log10_with(raw, SCALE, STRICT_GUARD, mode)
}