use crate::algos::exp::exp_generic as eg;
use crate::algos::support::ln_tang_table::ln_table_entry_baked;
use crate::algos::support::wide_trig_core::WideTrigCore;
use crate::int::types::compute_limbs::ComputeLimbs;
use crate::int::types::traits::BigInt;
use crate::support::rounding::RoundingMode;
const M: u32 = 128;
pub(crate) const EXTERNAL_EXTRA_DIGITS: u32 = 12;
#[inline]
pub(crate) fn tang_ln_fixed<
C: WideTrigCore,
const CAP: u128,
const INTERNAL_EXTRA: bool,
const SCALE: u32,
>(
v_w: C::W,
w: u32,
) -> C::W
where
<C::W as BigInt>::Scratch: ComputeLimbs,
{
tang_ln_fixed_g::<C::W, CAP, INTERNAL_EXTRA>(v_w, w, |ww| C::ln2::<SCALE>(ww))
}
#[inline]
pub(crate) fn tang_ln_fixed_g<S: BigInt, const CAP: u128, const INTERNAL_EXTRA: bool>(
v_w: S,
w: u32,
ln2: impl Fn(u32) -> S,
) -> S
where
S::Scratch: ComputeLimbs,
{
let (w_ext, v_ext, extra): (u32, S, u32) = if INTERNAL_EXTRA {
let extra = EXTERNAL_EXTRA_DIGITS;
let v_ext = v_w * eg::pow10::<S>(extra);
(w + extra, v_ext, extra)
} else {
(w, v_w, 0)
};
let one_w = eg::one::<S>(w_ext);
let pow10_w = one_w;
let two_w = one_w + one_w;
let mut k: i32 = eg::bit_length::<S>(v_ext) as i32 - eg::bit_length::<S>(one_w) as i32;
let m_w = loop {
let m = if k >= 0 {
v_ext >> (k as u32)
} else {
v_ext << ((-k) as u32)
};
if m >= two_w {
k += 1;
} else if m < one_w {
k -= 1;
} else {
break m;
}
};
let result_at_w_ext = if m_w == one_w {
eg::scale_by_k::<S>(ln2(w_ext), k as i128)
} else {
let i_raw = ((m_w - one_w) * eg::lit::<S>(M as i128)) / one_w;
let i_i128 = BigInt::to_i128(i_raw);
let i_idx = if i_i128 >= M as i128 {
(M - 1) as usize
} else {
i_i128 as usize
};
let f_i = one_w + (one_w * eg::lit::<S>(i_idx as i128)) / eg::lit::<S>(M as i128);
let t = eg::div_cached::<S>(m_w - f_i, m_w + f_i, pow10_w);
let t2 = eg::mul::<S>(t, t, w_ext);
let mut sum = t;
let mut term = t;
let mut j: u128 = 1;
loop {
term = eg::mul::<S>(term, t2, w_ext);
let contrib = term / eg::lit::<S>((2 * j + 1) as i128);
if contrib == eg::zero::<S>() {
break;
}
sum = sum + contrib;
j += 1;
if j > CAP {
break;
}
}
let ln_m = sum + sum + ln_table_entry_baked::<S>(w_ext, i_idx, pow10_w);
eg::scale_by_k::<S>(ln2(w_ext), k as i128) + ln_m
};
if !INTERNAL_EXTRA || extra == 0 {
result_at_w_ext
} else {
let p = eg::pow10::<S>(extra);
let (q_signed, has_residue) = if result_at_w_ext >= eg::zero::<S>() {
let q = result_at_w_ext / p;
let has = result_at_w_ext - q * p != eg::zero::<S>();
(q, has)
} else {
let abs_v = -result_at_w_ext;
let q = abs_v / p;
let has = abs_v - q * p != eg::zero::<S>();
(-q, has)
};
if has_residue {
if q_signed >= eg::zero::<S>() {
q_signed + eg::lit::<S>(1)
} else {
q_signed - eg::lit::<S>(1)
}
} else {
q_signed
}
}
}
#[inline]
#[must_use]
pub(crate) fn ln_tang_g<
C: WideTrigCore,
Wk: BigInt,
const SCALE: u32,
const GUARD: u32,
const CAP: u128,
const DIRECTED: bool,
const INTERNAL_EXTRA: bool,
>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<Wk as BigInt>::Scratch: ComputeLimbs,
<C::W as BigInt>::Scratch: ComputeLimbs,
{
use crate::algos::support::wide_trig_core::{
round_to_storage_directed_widening_g, round_to_storage_with_g, to_work_scaled_g,
};
use crate::support::rounding::DEFAULT_ROUNDING_MODE;
if raw <= C::storage_zero() {
panic!("wide-tier ln: argument must be positive");
}
let ln2 = |ww: u32| -> Wk {
if ww == SCALE + GUARD {
crate::consts::ln2_by_scale::<Wk>(SCALE + GUARD, DEFAULT_ROUNDING_MODE)
} else {
crate::consts::ln2_by_working_scale::<Wk>(ww, DEFAULT_ROUNDING_MODE)
}
};
let ln2_w = |ww: u32| -> C::W {
if ww == SCALE + GUARD {
crate::consts::ln2_by_scale::<C::W>(SCALE + GUARD, DEFAULT_ROUNDING_MODE)
} else {
crate::consts::ln2_by_working_scale::<C::W>(ww, DEFAULT_ROUNDING_MODE)
}
};
if DIRECTED {
let use_extra = INTERNAL_EXTRA && {
let one = C::storage_one(SCALE);
let diff = if raw >= one { raw - one } else { one - raw };
diff.bit_length() < (SCALE - SCALE / 4) * 3
};
let r = if use_extra {
round_to_storage_directed_widening_g::<C::Storage, Wk, C::W>(
GUARD, SCALE, mode, C::storage_max(), C::storage_min(),
|guard| {
tang_ln_fixed_g::<Wk, CAP, true>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard), SCALE + guard, ln2,
)
},
|guard| {
tang_ln_fixed_g::<C::W, CAP, true>(
to_work_scaled_g::<C::Storage, C::W>(raw, guard), SCALE + guard, ln2_w,
)
},
)
} else {
round_to_storage_directed_widening_g::<C::Storage, Wk, C::W>(
GUARD, SCALE, mode, C::storage_max(), C::storage_min(),
|guard| {
tang_ln_fixed_g::<Wk, CAP, false>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard), SCALE + guard, ln2,
)
},
|guard| {
tang_ln_fixed_g::<C::W, CAP, false>(
to_work_scaled_g::<C::Storage, C::W>(raw, guard), SCALE + guard, ln2_w,
)
},
)
};
crate::algos::support::wide_trig_core::adjust_ln_near_one::<C, SCALE>(r, raw, mode)
} else {
let w = SCALE + GUARD;
let r = tang_ln_fixed_g::<Wk, CAP, INTERNAL_EXTRA>(
to_work_scaled_g::<C::Storage, Wk>(raw, GUARD), w, ln2,
);
round_to_storage_with_g::<C::Storage, Wk>(
r, w, SCALE, mode, C::storage_max(), C::storage_min(),
)
}
}
#[inline]
#[must_use]
pub(crate) fn ln_tang<
C: WideTrigCore,
const SCALE: u32,
const GUARD: u32,
const CAP: u128,
const DIRECTED: bool,
const INTERNAL_EXTRA: bool,
>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: ComputeLimbs,
{
ln_tang_g::<C, C::W, SCALE, GUARD, CAP, DIRECTED, INTERNAL_EXTRA>(raw, mode)
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "d462")]
fn ln_d462_s231_power_of_two_short_circuit() {
let x: crate::D462<231> = "2.0".parse().unwrap();
let r = x.ln();
let expect: crate::D462<231> = "0.693147180559945309417232121458176568075500134360255254120680009493393621969694715605863326996418687542001481020570685733685520235758130557032670751635075961930727570828371435190307038623891673471123350115364497955239120475172681575".parse().unwrap();
assert_eq!(r, expect);
}
#[test]
#[cfg(feature = "d115")]
fn ln_d115_s0_routes_and_rounds() {
let x: crate::D115<0> = "2".parse().unwrap();
let one: crate::D115<0> = "1".parse().unwrap();
assert_eq!(x.ln(), one);
}
}