pub(crate) mod exp_generic {
#![allow(unused)]
use crate::wide_int::WideStorage;
use crate::support::rounding::RoundingMode;
const SERIES_CAP: u128 = 20_000;
#[inline]
fn lit<S: WideStorage>(n: i128) -> S {
S::from_i128(n)
}
#[inline]
fn zero<S: WideStorage>() -> S {
S::ZERO
}
#[inline]
fn abs<S: WideStorage>(v: S) -> S {
if v < S::ZERO { -v } else { v }
}
#[inline]
fn pow10<S: WideStorage>(n: u32) -> S {
S::TEN.pow(n)
}
#[inline]
fn one<S: WideStorage>(w: u32) -> S {
pow10::<S>(w)
}
fn bit_length<S: WideStorage>(v: S) -> u32 {
S::BITS - abs(v).leading_zeros()
}
#[inline]
fn round_div<S: WideStorage>(n: S, d: S) -> S {
let (q, r) = n.div_rem(d);
if r == S::ZERO {
return q;
}
let ar = abs(r);
let comp = abs(d) - ar;
let cmp_r = if ar < comp {
::core::cmp::Ordering::Less
} else if ar > comp {
::core::cmp::Ordering::Greater
} else {
::core::cmp::Ordering::Equal
};
let q_is_odd = q.bit(0);
let result_positive = (n < S::ZERO) == (d < S::ZERO);
if crate::support::rounding::should_bump(
RoundingMode::HalfToEven,
cmp_r,
q_is_odd,
result_positive,
) {
if result_positive { q + S::ONE } else { q - S::ONE }
} else {
q
}
}
#[inline]
fn round_div_pow10<S: WideStorage>(n: S, w: u32) -> S {
if w == 0 {
return n;
}
round_div(n, pow10::<S>(w))
}
#[inline]
fn mul<S: WideStorage>(a: S, b: S, w: u32) -> S {
round_div_pow10(a * b, w)
}
#[inline]
fn mul_cached<S: WideStorage>(a: S, b: S, pow10_w: S) -> S {
round_div(a * b, pow10_w)
}
#[inline]
fn div_cached<S: WideStorage>(a: S, b: S, pow10_w: S) -> S {
round_div(a * pow10_w, b)
}
#[inline]
fn mul_u<S: WideStorage>(a: S, n: u128) -> S {
if n <= u64::MAX as u128 {
a.checked_mul_u64(n as u64)
} else {
a * S::from_i128(n as i128)
}
}
#[inline]
fn scale_by_k<S: WideStorage>(c: S, k: i128) -> S {
if k >= 0 {
mul_u(c, k as u128)
} else {
-mul_u(c, k.unsigned_abs())
}
}
fn round_to_nearest_int<S: WideStorage>(v: S, w: u32) -> i128 {
let divisor = pow10::<S>(w);
let (q, r) = v.div_rem(divisor);
let half = divisor >> 1;
let qi = if abs(r) >= half {
if v < S::ZERO { q - S::ONE } else { q + S::ONE }
} else {
q
};
crate::wide_int::wide_cast::<S, i128>(qi)
}
fn ln2<S: WideStorage>(w: u32) -> S {
let t = one::<S>(w) / lit::<S>(3);
let t2 = mul(t, t, w);
let mut sum = t;
let mut term = t;
let mut j: u128 = 1;
loop {
term = mul(term, t2, w);
let contrib = term / lit::<S>((2 * j + 1) as i128);
if contrib == S::ZERO {
break;
}
sum = sum + contrib;
j += 1;
if j > SERIES_CAP {
break;
}
}
sum + sum
}
pub(crate) fn exp_fixed<S: WideStorage>(v_w: S, w: u32) -> S {
let one_w_pre = one::<S>(w);
let l2_pre = ln2::<S>(w);
let pow10_w_pre = one_w_pre;
let k = round_to_nearest_int(div_cached(v_w, l2_pre, pow10_w_pre), w);
let abs_k_u128 = if k < 0 { -k } else { k } as u128;
let extra: u32 = if abs_k_u128 == 0 {
0
} else {
let digits = (abs_k_u128 * 30103 + 99_999) / 100_000;
let capped = digits.min((S::BITS / 4) as u128) as u32;
capped + 12 + (capped >> 2)
};
let w_ext = w + extra;
let v_ext = if extra == 0 { v_w } else { v_w * pow10::<S>(extra) };
let one_w = one::<S>(w_ext);
let l2 = ln2::<S>(w_ext);
let pow10_w = one_w;
let s = v_ext - scale_by_k(l2, k);
let p_bits = w_ext.saturating_mul(3).saturating_add(1);
let mut n: u32 = 1;
while (n + 1) * (n + 1) <= p_bits {
n += 1;
}
let s_red = s >> n;
let mut sum = one_w + s_red;
let mut term = s_red;
let mut iter: u128 = 2;
loop {
term = mul_cached(term, s_red, pow10_w) / lit::<S>(iter as i128);
if term == S::ZERO {
break;
}
sum = sum + term;
iter += 1;
if iter > SERIES_CAP {
break;
}
}
let mut squared = sum;
let mut i = 0;
while i < n {
squared = mul_cached(squared, squared, pow10_w);
i += 1;
}
let sum = squared;
let scaled_at_w_ext = if k >= 0 {
let shift = k as u32;
if bit_length(sum) + shift >= S::BITS {
panic!("exp_generic::exp_fixed: result overflows the working width");
}
sum << shift
} else {
let neg_k = -k as u128;
if neg_k >= bit_length(sum) as u128 {
return zero::<S>();
}
sum >> (neg_k as u32)
};
if extra == 0 {
scaled_at_w_ext
} else {
round_div_pow10(scaled_at_w_ext, extra)
}
}
#[inline]
fn div<S: WideStorage>(a: S, b: S, w: u32) -> S {
round_div(a * pow10::<S>(w), b)
}
pub(crate) fn sinh_pos<S: WideStorage>(av_w: S, w: u32) -> S {
let ex = exp_fixed::<S>(av_w, w);
let enx = div(one::<S>(w), ex, w);
(ex - enx) >> 1
}
pub(crate) fn cosh_pos<S: WideStorage>(av_w: S, w: u32) -> S {
let ex = exp_fixed::<S>(av_w, w);
let enx = div(one::<S>(w), ex, w);
(ex + enx) >> 1
}
pub(crate) fn tanh_pos<S: WideStorage>(av_w: S, w: u32) -> S {
let ex = exp_fixed::<S>(av_w, w);
let enx = div(one::<S>(w), ex, w);
div(ex - enx, ex + enx, w)
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! decl_pow10_cached {
(with_const_table, $max_scale:literal) => {
pub(crate) const POW10_TABLE_MAX_W: u32 = ($max_scale as u32) + GUARD;
pub(crate) static POW10_TABLE: [W; (POW10_TABLE_MAX_W + 1) as usize] = {
let mut table = [<W>::from_u128(0); (POW10_TABLE_MAX_W + 1) as usize];
let ten = <W>::from_u128(10);
table[0] = <W>::from_u128(1);
let mut i: usize = 1;
let len = (POW10_TABLE_MAX_W + 1) as usize;
while i < len {
table[i] = table[i - 1].wrapping_mul(ten);
i += 1;
}
table
};
#[inline]
pub(crate) fn pow10_cached(w: u32) -> W {
if w <= POW10_TABLE_MAX_W {
return unsafe { *POW10_TABLE.get_unchecked(w as usize) };
}
cached(&POW10_CACHE_GET, w, pow10)
}
};
(no_const_table, $max_scale:literal) => {
#[inline]
pub(crate) fn pow10_cached(w: u32) -> W {
cached(&POW10_CACHE_GET, w, pow10)
}
};
}
pub(crate) use decl_pow10_cached;
macro_rules! decl_wide_transcendental {
($Type:ident, $Storage:ty, $Work:ty, $Wexp:ty, $core:ident, $max_scale:literal) => {
$crate::macros::wide_transcendental::decl_wide_transcendental!(
$Type, $Storage, $Work, $Wexp, $core, $max_scale, with_const_table
);
};
($Type:ident, $Storage:ty, $Work:ty, $Wexp:ty, $core:ident, $max_scale:literal, $table_mode:ident) => {
pub(crate) mod $core {
#![allow(unused)]
pub(crate) type W = $Work;
pub(crate) type Wexp = $Wexp;
pub(crate) const GUARD: u32 = 30;
#[inline]
pub(crate) const fn guard_agm(scale: u32) -> u32 {
if scale > GUARD - 4 {
scale - GUARD + 4
} else {
4
}
}
pub(crate) fn exp_agm_k_lift_from_w(v_w_at_scale: W, scale: u32) -> u32 {
let av = abs(v_w_at_scale);
let bl_v = bit_length(av);
let bl_one_s = bit_length(pow10_cached(scale));
if bl_v <= bl_one_s {
return 5;
}
let log2_int_part = bl_v - bl_one_s + 1;
let int_part_upper = if log2_int_part >= 128 {
u128::MAX
} else {
1u128 << log2_int_part
};
let k_lift_u128 = int_part_upper.saturating_mul(4343) / 10000 + 5;
if k_lift_u128 > u32::MAX as u128 {
u32::MAX
} else {
k_lift_u128 as u32
}
}
const SERIES_CAP: u128 = 20_000;
#[inline]
pub(crate) fn lit(n: u128) -> W {
$crate::wide_int::wide_cast(n)
}
#[inline]
pub(crate) fn zero() -> W {
lit(0)
}
#[inline]
fn abs(v: W) -> W {
if v < lit(0) { -v } else { v }
}
#[inline]
pub(crate) fn pow10(n: u32) -> W {
lit(10).pow(n)
}
$crate::macros::wide_transcendental::decl_pow10_cached!($table_mode, $max_scale);
#[inline]
pub(crate) fn one(w: u32) -> W {
pow10_cached(w)
}
#[inline]
fn round_div(n: W, d: W) -> W {
let (q, r) = n.div_rem(d);
if r == lit(0) {
return q;
}
let ar = abs(r);
let comp = abs(d) - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = (n < lit(0)) == (d < lit(0));
if $crate::support::rounding::should_bump(
$crate::support::rounding::RoundingMode::HalfToEven,
cmp_r,
q_is_odd,
result_positive,
) {
if result_positive { q + lit(1) } else { q - lit(1) }
} else {
q
}
}
#[inline]
fn round_div_pow10(n: W, w: u32) -> W {
if w == 0 {
return n;
}
if w <= 38 {
return $crate::algos::mg_divide::div_wide_pow10_with::<W, { <W as $crate::wide_int::WideInt>::U128_LIMBS }>(
n,
w,
$crate::support::rounding::RoundingMode::HalfToEven,
);
}
$crate::algos::newton_reciprocal::dispatch_wide_pow10_with::<W, { <W as $crate::wide_int::WideInt>::U128_LIMBS }>(
n,
w,
$crate::support::rounding::RoundingMode::HalfToEven,
)
}
#[inline]
pub(crate) fn mul(a: W, b: W, w: u32) -> W {
round_div_pow10(a * b, w)
}
#[inline]
pub(crate) fn mul_cached(a: W, b: W, pow10_w: W) -> W {
round_div(a * b, pow10_w)
}
#[inline]
pub(crate) fn div(a: W, b: W, w: u32) -> W {
round_div(a * pow10_cached(w), b)
}
#[inline]
pub(crate) fn div_cached(a: W, b: W, pow10_w: W) -> W {
round_div(a * pow10_w, b)
}
#[inline]
fn mul_u(a: W, n: u128) -> W {
if n <= u64::MAX as u128 {
a.checked_mul_u64(n as u64)
} else {
a * lit(n)
}
}
pub(crate) fn bit_length(v: W) -> u32 {
W::BITS - abs(v).leading_zeros()
}
pub(crate) fn sqrt_fixed(v: W, w: u32) -> W {
let av = abs(v);
debug_assert!(
bit_length(av) + (w as u32) * 4 < W::BITS,
"sqrt_fixed: |v| * 10^w overflows the working width"
);
let n = av * pow10_cached(w);
#[cfg(feature = "std")]
{
if bit_length(n) < 1000 && n > zero() {
let seed_f64 = n.as_f64().sqrt();
let seed = W::from_f64(seed_f64);
let x0 = if seed <= zero() { lit(1) } else { seed };
let mut x = (x0 + n / x0) >> 1;
loop {
let y = (x + n / x) >> 1;
if y >= x {
return x;
}
x = y;
}
}
}
n.isqrt()
}
pub(crate) fn to_work(raw: $Storage) -> W {
$crate::wide_int::wide_cast::<$Storage, W>(raw) * pow10_cached(GUARD)
}
pub(crate) fn to_work_w(raw: $Storage, working_digits: u32) -> W {
$crate::wide_int::wide_cast::<$Storage, W>(raw) * pow10_cached(working_digits)
}
pub(crate) fn round_to_storage(v: W, w: u32, target: u32) -> $Storage {
round_to_storage_with(v, w, target, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
pub(crate) fn round_to_storage_with(
v: W,
w: u32,
target: u32,
mode: $crate::support::rounding::RoundingMode,
) -> $Storage {
let shift = w - target;
let rounded = if shift == 0 {
v
} else if shift <= 38 {
$crate::algos::mg_divide::div_wide_pow10_with::<W, { <W as $crate::wide_int::WideInt>::U128_LIMBS }>(v, shift, mode)
} else {
$crate::algos::newton_reciprocal::dispatch_wide_pow10_with::<W, { <W as $crate::wide_int::WideInt>::U128_LIMBS }>(v, shift, mode)
};
let max_w = $crate::wide_int::wide_cast::<$Storage, W>(<$Storage>::MAX);
let min_w = $crate::wide_int::wide_cast::<$Storage, W>(<$Storage>::MIN);
if rounded > max_w || rounded < min_w {
panic!(concat!(
stringify!($Type),
" strict transcendental: result out of range"
));
}
$crate::wide_int::wide_cast::<W, $Storage>(rounded)
}
pub(crate) fn round_to_storage_directed(
base_guard: u32,
target: u32,
mode: $crate::support::rounding::RoundingMode,
recompute: impl FnMut(u32) -> W,
) -> $Storage {
round_to_storage_directed_impl(base_guard, target, mode, false, recompute)
}
pub(crate) fn round_to_storage_directed_near_special(
base_guard: u32,
target: u32,
mode: $crate::support::rounding::RoundingMode,
recompute: impl FnMut(u32) -> W,
) -> $Storage {
round_to_storage_directed_impl(base_guard, target, mode, true, recompute)
}
fn round_to_storage_directed_impl(
base_guard: u32,
target: u32,
mode: $crate::support::rounding::RoundingMode,
force_confirm: bool,
mut recompute: impl FnMut(u32) -> W,
) -> $Storage {
use $crate::support::rounding::{is_nearest_mode, RoundingMode};
let base_w = target + base_guard;
if is_nearest_mode(mode) {
if !force_confirm {
return round_to_storage_with(recompute(base_guard), base_w, target, mode);
}
let mut nearest_narrow = |guard: u32| -> $Storage {
let w = target + guard;
round_to_storage_with(recompute(guard), w, target, mode)
};
let lo = nearest_narrow(base_guard);
let int_digits = {
let n = $crate::wide_int::wide_cast::<$Storage, W>(lo);
let m = if n < lit(0) { -n } else { n };
let bl = bit_length(m);
let storage_digits = (bl as u64 * 30103 / 100_000) as u32 + 1;
storage_digits.saturating_sub(target)
};
let cap_digits = (<W>::BITS / 8).saturating_sub(int_digits + 8);
let max_guard = cap_digits.saturating_sub(target).max(base_guard);
let mut guard = base_guard;
let mut best = lo;
loop {
if guard >= max_guard {
break;
}
let step = (target + base_guard).max(base_guard);
let next_guard = guard.saturating_add(step).min(max_guard);
let hi = nearest_narrow(next_guard);
if hi == best {
break;
}
guard = next_guard;
best = hi;
}
return best;
}
let mut directed_narrow = |guard: u32| -> (W, W, W) {
let w = target + guard;
let v = recompute(guard);
let shift = w - target;
let neg = v < lit(0);
let mag = if neg { -v } else { v };
let divisor = pow10(shift);
let (q, rem) = mag.div_rem(divisor);
let result_positive = !neg;
let bump = rem != lit(0)
&& match mode {
RoundingMode::Trunc => false,
RoundingMode::Floor => !result_positive,
RoundingMode::Ceiling => result_positive,
_ => unreachable!(),
};
let q_mag = if bump { q + lit(1) } else { q };
let signed = if neg { -q_mag } else { q_mag };
let dist = if rem < divisor - rem { rem } else { divisor - rem };
(signed, dist, divisor)
};
let (mut lo, dist0, divisor0) = directed_narrow(base_guard);
let band0 = divisor0 / lit(1000);
let near_grid = force_confirm || dist0 <= band0;
let signed = if !near_grid {
lo
} else {
let int_digits = {
let m = if lo < lit(0) { -lo } else { lo };
let bl = bit_length(m);
let storage_digits = (bl as u64 * 30103 / 100_000) as u32 + 1;
storage_digits.saturating_sub(target)
};
let cap_digits = (<W>::BITS / 8)
.saturating_sub(int_digits + 8);
let max_guard = cap_digits.saturating_sub(target).max(base_guard);
let mut guard = base_guard;
loop {
if guard >= max_guard {
break lo;
}
let step = (target + base_guard).max(base_guard);
let next_guard = guard.saturating_add(step).min(max_guard);
let (hi, _, _) = directed_narrow(next_guard);
if hi == lo {
break lo;
}
guard = next_guard;
lo = hi;
}
};
let max_w = $crate::wide_int::wide_cast::<$Storage, W>(<$Storage>::MAX);
let min_w = $crate::wide_int::wide_cast::<$Storage, W>(<$Storage>::MIN);
if signed > max_w || signed < min_w {
panic!(concat!(
stringify!($Type),
" strict transcendental: result out of range"
));
}
$crate::wide_int::wide_cast::<W, $Storage>(signed)
}
pub(crate) fn round_to_nearest_int(v: W, w: u32) -> i128 {
let divisor = pow10_cached(w);
let (q, r) = v.div_rem(divisor);
let half = divisor >> 1;
let qi = if abs(r) >= half {
if v < lit(0) { q - lit(1) } else { q + lit(1) }
} else {
q
};
$crate::wide_int::wide_cast::<W, i128>(qi)
}
pub(crate) fn log_is_exact_int(value_raw: W, base_raw: W, scale: u32, k: i128) -> bool {
let one_s = pow10_cached(scale);
if k == 0 {
return value_raw == one_s;
}
let (bq, br) = base_raw.div_rem(one_s);
if br != lit(0) {
return false;
}
let base_int = bq;
let kk = k.unsigned_abs();
let limit_bits = W::BITS - 4;
if k > 0 {
let (vq, vr) = value_raw.div_rem(one_s);
if vr != lit(0) {
return false;
}
let value_int = vq;
let mut pow = lit(1);
let mut i: u128 = 0;
while i < kk {
if bit_length(pow) + bit_length(base_int) >= limit_bits {
return false;
}
pow = pow * base_int;
i += 1;
}
pow == value_int
} else {
let mut cur = value_raw;
let mut i: u128 = 0;
while i < kk {
if bit_length(cur) + bit_length(base_int) >= limit_bits {
return false;
}
cur = cur * base_int;
i += 1;
}
cur == one_s
}
}
pub(crate) fn exact_int_at_scale(k: i128, scale: u32) -> $Storage {
narrow_to_storage(scale_by_k(one(scale), k))
}
pub(crate) fn narrow_to_storage(v: W) -> $Storage {
let max_w = $crate::wide_int::wide_cast::<$Storage, W>(<$Storage>::MAX);
let min_w = $crate::wide_int::wide_cast::<$Storage, W>(<$Storage>::MIN);
if v > max_w || v < min_w {
panic!(concat!(
stringify!($Type),
" strict transcendental: result out of range"
));
}
$crate::wide_int::wide_cast::<W, $Storage>(v)
}
pub(crate) fn exp2_exact_pin(raw: $Storage, scale: u32) -> ::core::option::Option<$Storage> {
let raw_w = wide_cast_storage(raw);
let one_s = pow10_cached(scale);
let (kq, kr) = raw_w.div_rem(one_s);
if kr != lit(0) {
return ::core::option::Option::None;
}
let k = $crate::wide_int::wide_cast::<W, i128>(kq);
exp2_exact_pow(k, scale).map(narrow_to_storage)
}
#[inline]
fn wide_cast_storage(raw: $Storage) -> W {
$crate::wide_int::wide_cast::<$Storage, W>(raw)
}
pub(crate) fn exp_lift_cap(needed: u128, scale: u32) -> u32 {
let wexp_digits = <Wexp>::BITS as u128 * 30103 / 100_000;
let base = 2 * (scale as u128 + GUARD as u128) + 64;
let head = wexp_digits.saturating_sub(base) * 10 / 45;
let lift = needed.min(head);
if lift > u32::MAX as u128 { u32::MAX } else { lift as u32 }
}
pub(crate) fn exp2_result_int_digits(raw: $Storage, scale: u32) -> u32 {
exp_lift_cap(pow_result_digits(abs(wide_cast_storage(raw)), scale, 30103), scale)
}
pub(crate) fn exp_result_int_digits(mag_at_scale: W, scale: u32) -> u32 {
exp_lift_cap(pow_result_digits(abs(mag_at_scale), scale, 43429), scale)
}
fn pow_result_digits(av: W, scale: u32, factor: u128) -> u128 {
let bl_v = bit_length(av);
let bl_one = bit_length(pow10_cached(scale));
if bl_v <= bl_one {
return 0;
}
let log2_int = bl_v - bl_one + 1;
let int_upper = if log2_int >= 127 { u128::MAX } else { 1u128 << log2_int };
(int_upper.saturating_mul(factor) / 100_000) as u128
}
pub(crate) fn exp2_exact_pow(k: i128, scale: u32) -> ::core::option::Option<W> {
let one_s = pow10_cached(scale);
if k == 0 {
return ::core::option::Option::Some(one_s);
}
let kk = k.unsigned_abs();
if k > 0 {
let mut v = one_s;
let two = lit(2);
let mut i: u128 = 0;
while i < kk {
if bit_length(v) + 2 >= W::BITS - 4 {
return ::core::option::Option::None;
}
v = v * two;
i += 1;
}
::core::option::Option::Some(v)
} else {
if (kk as u128) > scale as u128 {
return ::core::option::Option::None;
}
let mut v = pow10_cached(scale - kk as u32);
let five = lit(5);
let mut i: u128 = 0;
while i < kk {
if bit_length(v) + 3 >= W::BITS - 4 {
return ::core::option::Option::None;
}
v = v * five;
i += 1;
}
::core::option::Option::Some(v)
}
}
#[inline]
fn scale_by_k(c: W, k: i128) -> W {
if k >= 0 {
mul_u(c, k as u128)
} else {
-mul_u(c, k.unsigned_abs())
}
}
pub(crate) fn ln2(w: u32) -> W {
cached(&LN2_CACHE_GET, w, ln2_compute)
}
fn ln2_compute(w: u32) -> W {
let t = one(w) / lit(3);
let t2 = mul(t, t, w);
let mut sum = t;
let mut term = t;
let mut j: u128 = 1;
loop {
term = mul(term, t2, w);
let contrib = term / lit(2 * j + 1);
if contrib == zero() {
break;
}
sum = sum + contrib;
j += 1;
if j > SERIES_CAP {
break;
}
}
sum + sum
}
pub(crate) fn ln_fixed(v_w: W, w: u32) -> W {
let one_w = one(w);
let two_w = one_w + one_w;
let pow10_w = one_w;
let mut k: i32 = bit_length(v_w) as i32 - bit_length(one_w) as i32;
let mut m_w = loop {
let m = if k >= 0 {
v_w >> (k as u32)
} else {
v_w << ((-k) as u32)
};
if m >= two_w {
k += 1;
} else if m < one_w {
k -= 1;
} else {
break m;
}
};
let p_bits = w.saturating_mul(3).saturating_add(1);
let mut sqrt_l: u32 = 0;
{
let mut n: u32 = 0;
while (n + 1) * (n + 1) <= p_bits {
n += 1;
}
sqrt_l = n / 4;
}
let mut i = 0;
while i < sqrt_l {
m_w = sqrt_fixed(m_w, w);
i += 1;
}
let t = div_cached(m_w - one_w, m_w + one_w, pow10_w);
let t2 = mul_cached(t, t, pow10_w);
let mut sum = t;
let mut term = t;
let mut j: u128 = 1;
loop {
term = mul_cached(term, t2, pow10_w);
let contrib = term / lit(2 * j + 1);
if contrib == zero() {
break;
}
sum = sum + contrib;
j += 1;
if j > SERIES_CAP {
break;
}
}
let ln_m = sum << (sqrt_l + 1);
scale_by_k(ln2(w), k as i128) + ln_m
}
pub(crate) fn log1p_fixed(t: W, w: u32) -> W {
let one_w = one(w);
let two_w = one_w + one_w;
let pow10_w = one_w;
let u = div_cached(t, two_w + t, pow10_w);
let u2 = mul_cached(u, u, pow10_w);
let mut sum = u;
let mut term = u;
let mut j: u128 = 1;
loop {
term = mul_cached(term, u2, pow10_w);
let contrib = term / lit(2 * j + 1);
if contrib == zero() {
break;
}
sum = sum + contrib;
j += 1;
if j > SERIES_CAP {
break;
}
}
sum + sum
}
pub(crate) fn expm1_fixed(s: W, w: u32) -> W {
let pow10_w = one(w);
let mut sum = s;
let mut term = s;
let mut iter: u128 = 2;
loop {
term = mul_cached(term, s, pow10_w) / lit(iter);
if term == zero() {
break;
}
sum = sum + term;
iter += 1;
if iter > SERIES_CAP {
break;
}
}
sum
}
pub(crate) fn ln10(w: u32) -> W {
cached(&LN10_CACHE_GET, w, ln10_compute)
}
fn ln10_compute(w: u32) -> W {
ln_fixed(one(w) * lit(10), w)
}
pub(crate) fn ln_fixed_agm(v_w: W, w: u32) -> W {
let one_w = one(w);
let p_bits = ((w as i32) * 332 + 99) / 100;
let bl_v = bit_length(v_w) as i32;
let bl_one = bit_length(one_w) as i32;
let safety = 2 + ((p_bits.max(1) as u32).ilog2() / 2) as i32;
let mut m: i32 = (p_bits / 2) + safety + bl_one - bl_v;
if m < 2 {
m = 2;
}
let cap = (W::BITS as i32) - bl_v - 2;
if cap > 0 && m > cap {
m = cap;
}
debug_assert!(
m > 0,
"ln_fixed_agm: working-int width too small for this scale"
);
let s_w = v_w << (m as u32);
let y_w = div(lit(4) * one_w, s_w, w);
let mut a = one_w;
let mut b = y_w;
let iter_cap = 80u32;
let pow10_w_agm = pow10_cached(w);
for _ in 0..iter_cap {
let next_a = (a + b) >> 1;
let next_b = sqrt_fixed(mul_cached(a, b, pow10_w_agm), w);
let d = if next_a >= next_b { next_a - next_b } else { next_b - next_a };
a = next_a;
b = next_b;
if d <= lit(2) {
break;
}
}
let pi_w = pi(w);
let agm_part = div(pi_w, a + a, w);
agm_part - scale_by_k(ln2(w), m as i128)
}
pub(crate) fn exp_fixed_agm(v_w: W, w: u32) -> W {
let one_w = one(w);
let l2 = ln2(w);
let k = round_to_nearest_int(div(v_w, l2, w), w);
let s = v_w - scale_by_k(l2, k);
let s2 = mul(s, s, w);
let mut x = one_w + s + (s2 >> 1);
if x <= lit(0) {
x = one_w;
}
let iter_cap = 80u32;
for _ in 0..iter_cap {
let ln_x = ln_fixed_agm(x, w);
let delta = s - ln_x;
if abs(delta) <= lit(2) {
x = mul(x, one_w + delta, w);
break;
}
x = mul(x, one_w + delta, w);
}
if k >= 0 {
let shift = k as u32;
if bit_length(x) + shift >= W::BITS {
panic!(concat!(
stringify!($Type),
"::exp: result overflows the representable range"
));
}
x << shift
} else {
let neg_k = (-k) as u128;
if neg_k >= bit_length(x) as u128 {
return zero();
}
x >> (neg_k as u32)
}
}
pub(crate) fn exp_fixed(v_w: W, w: u32) -> W {
#[cfg(feature = "perf-trace")]
let _exp_span = $crate::tracing::info_span!(concat!(
stringify!($Type), "::exp_fixed"
)).entered();
#[cfg(feature = "perf-trace")]
let _reduce_span = $crate::tracing::info_span!("range_reduce").entered();
let one_w_pre = one(w);
let l2_pre = ln2(w);
let pow10_w_pre = one_w_pre;
let k = round_to_nearest_int(div_cached(v_w, l2_pre, pow10_w_pre), w);
let abs_k_u128 = if k < 0 { -k } else { k } as u128;
let extra: u32 = if abs_k_u128 == 0 {
0
} else {
let digits = (abs_k_u128 * 30103 + 99_999) / 100_000;
let capped = digits.min((<W>::BITS / 4) as u128) as u32;
capped + 12 + (capped >> 2)
};
let w_ext = w + extra;
let v_ext = if extra == 0 { v_w } else { v_w * pow10(extra) };
let one_w = one(w_ext);
let l2 = ln2(w_ext);
let pow10_w = one_w;
let s = v_ext - scale_by_k(l2, k);
let p_bits = w_ext.saturating_mul(3).saturating_add(1);
let mut n: u32 = 1;
while (n + 1) * (n + 1) <= p_bits {
n += 1;
}
let s_red = s >> n;
#[cfg(feature = "perf-trace")]
drop(_reduce_span);
#[cfg(feature = "perf-trace")]
let _taylor_span = $crate::tracing::info_span!("taylor_series").entered();
let mut sum = one_w + s_red;
let mut term = s_red;
let mut iter: u128 = 2;
loop {
term = mul_cached(term, s_red, pow10_w) / lit(iter);
if term == zero() {
break;
}
sum = sum + term;
iter += 1;
if iter > SERIES_CAP {
break;
}
}
#[cfg(feature = "perf-trace")]
drop(_taylor_span);
#[cfg(feature = "perf-trace")]
let _sqr_span = $crate::tracing::info_span!("postfix_squarings").entered();
let mut squared = sum;
let mut i = 0;
while i < n {
squared = mul_cached(squared, squared, pow10_w);
i += 1;
}
let sum = squared;
#[cfg(feature = "perf-trace")]
drop(_sqr_span);
#[cfg(feature = "perf-trace")]
let _reasm_span = $crate::tracing::info_span!("reassemble").entered();
let scaled_at_w_ext = if k >= 0 {
let shift = k as u32;
if bit_length(sum) + shift >= W::BITS {
panic!(concat!(
stringify!($Type),
"::exp: result overflows the representable range"
));
}
sum << shift
} else {
let neg_k = -k as u128;
if neg_k >= bit_length(sum) as u128 {
return zero();
}
sum >> (neg_k as u32)
};
if extra == 0 {
scaled_at_w_ext
} else {
round_div_pow10(scaled_at_w_ext, extra)
}
}
pub(crate) fn exp_fixed_wide(v_w: W, w: u32) -> W {
let v_wide = $crate::wide_int::wide_cast::<W, Wexp>(v_w);
let r_wide = $crate::macros::wide_transcendental::exp_generic::exp_fixed::<Wexp>(v_wide, w);
$crate::wide_int::wide_cast::<Wexp, W>(r_wide)
}
pub(crate) fn sinh_pos_wide(av_w: W, w: u32) -> W {
let av_wide = $crate::wide_int::wide_cast::<W, Wexp>(av_w);
let r = $crate::macros::wide_transcendental::exp_generic::sinh_pos::<Wexp>(av_wide, w);
$crate::wide_int::wide_cast::<Wexp, W>(r)
}
pub(crate) fn cosh_pos_wide(av_w: W, w: u32) -> W {
let av_wide = $crate::wide_int::wide_cast::<W, Wexp>(av_w);
let r = $crate::macros::wide_transcendental::exp_generic::cosh_pos::<Wexp>(av_wide, w);
$crate::wide_int::wide_cast::<Wexp, W>(r)
}
pub(crate) fn tanh_pos_wide(av_w: W, w: u32) -> W {
let av_wide = $crate::wide_int::wide_cast::<W, Wexp>(av_w);
let r = $crate::macros::wide_transcendental::exp_generic::tanh_pos::<Wexp>(av_wide, w);
$crate::wide_int::wide_cast::<Wexp, W>(r)
}
pub(crate) fn atan_taylor(x: W, w: u32) -> W {
let pow10_w = pow10_cached(w);
let x2 = mul_cached(x, x, pow10_w);
let mut sum = x;
let mut term = x;
let mut k: u128 = 1;
loop {
term = mul_cached(term, x2, pow10_w);
let contrib = term / lit(2 * k + 1);
if contrib == zero() {
break;
}
if k % 2 == 1 {
sum = sum - contrib;
} else {
sum = sum + contrib;
}
k += 1;
if k > SERIES_CAP {
break;
}
}
sum
}
pub(crate) fn pi(w: u32) -> W {
cached(&PI_CACHE_GET, w, pi_compute)
}
fn pi_compute(w: u32) -> W {
let a = atan_taylor(one(w) / lit(5), w);
let b = atan_taylor(one(w) / lit(239), w);
mul_u(a, 16) - mul_u(b, 4)
}
#[cfg(feature = "std")]
fn cached<F>(slot_get: &dyn Fn() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>>, w: u32, compute: F) -> W
where
F: FnOnce(u32) -> W,
{
let slot = slot_get();
let hit = slot.with(|c| {
let cache = c.borrow();
for &(cw, cv) in cache.iter() {
if cw == w {
return ::core::option::Option::Some(cv);
}
}
::core::option::Option::None
});
if let ::core::option::Option::Some(v) = hit {
return v;
}
let v = compute(w);
slot.with(|c| {
c.borrow_mut().push((w, v));
});
v
}
#[cfg(not(feature = "std"))]
fn cached<F>(_slot_get: &(), w: u32, compute: F) -> W
where
F: FnOnce(u32) -> W,
{
compute(w)
}
#[cfg(feature = "std")]
fn pi_cache_get() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> {
::std::thread_local! {
static SLOT: ::core::cell::RefCell<alloc::vec::Vec<(u32, W)>> = const {
::core::cell::RefCell::new(alloc::vec::Vec::new())
};
}
&SLOT
}
#[cfg(feature = "std")]
fn ln2_cache_get() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> {
::std::thread_local! {
static SLOT: ::core::cell::RefCell<alloc::vec::Vec<(u32, W)>> = const {
::core::cell::RefCell::new(alloc::vec::Vec::new())
};
}
&SLOT
}
#[cfg(feature = "std")]
fn ln10_cache_get() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> {
::std::thread_local! {
static SLOT: ::core::cell::RefCell<alloc::vec::Vec<(u32, W)>> = const {
::core::cell::RefCell::new(alloc::vec::Vec::new())
};
}
&SLOT
}
#[cfg(feature = "std")]
fn pow10_cache_get() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> {
::std::thread_local! {
static SLOT: ::core::cell::RefCell<alloc::vec::Vec<(u32, W)>> = const {
::core::cell::RefCell::new(alloc::vec::Vec::new())
};
}
&SLOT
}
#[cfg(feature = "std")]
const PI_CACHE_GET: fn() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> = pi_cache_get;
#[cfg(feature = "std")]
const LN2_CACHE_GET: fn() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> = ln2_cache_get;
#[cfg(feature = "std")]
const LN10_CACHE_GET: fn() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> = ln10_cache_get;
#[cfg(feature = "std")]
const POW10_CACHE_GET: fn() -> &'static ::std::thread::LocalKey<::core::cell::RefCell<alloc::vec::Vec<(u32, W)>>> = pow10_cache_get;
#[cfg(not(feature = "std"))]
const PI_CACHE_GET: () = ();
#[cfg(not(feature = "std"))]
const LN2_CACHE_GET: () = ();
#[cfg(not(feature = "std"))]
const LN10_CACHE_GET: () = ();
#[cfg(not(feature = "std"))]
const POW10_CACHE_GET: () = ();
pub(crate) fn half_pi(w: u32) -> W {
pi(w) >> 1
}
fn sin_taylor(r: W, w: u32) -> W {
let pow10_w = pow10_cached(w);
let r2 = mul_cached(r, r, pow10_w);
let mut sum = r;
let mut term = r;
let mut k: u128 = 1;
loop {
term = mul_cached(term, r2, pow10_w) / lit((2 * k) * (2 * k + 1));
if term == zero() {
break;
}
if k % 2 == 1 {
sum = sum - term;
} else {
sum = sum + term;
}
k += 1;
if k > SERIES_CAP {
break;
}
}
sum
}
fn cos_taylor(r: W, w: u32) -> W {
let pow10_w = pow10_cached(w);
let r2 = mul_cached(r, r, pow10_w);
let one_w = one(w);
let mut sum = one_w;
let mut term = one_w;
let mut k: u128 = 1;
loop {
term = mul_cached(term, r2, pow10_w)
/ lit((2 * k - 1) * (2 * k));
if term == zero() {
break;
}
if k % 2 == 1 {
sum = sum - term;
} else {
sum = sum + term;
}
k += 1;
if k > SERIES_CAP {
break;
}
}
sum
}
pub(crate) fn sin_fixed(v_w: W, w: u32) -> W {
let pi_w = pi(w);
let tau = pi_w + pi_w;
let hp = pi_w >> 1;
let qp = hp >> 1; let q = round_to_nearest_int(div(v_w, tau, w), w);
let r = v_w - scale_by_k(tau, q);
let neg = r < zero();
let abs_r = if neg { -r } else { r };
let reduced = if abs_r >= hp { pi_w - abs_r } else { abs_r };
let s = if reduced > qp {
cos_taylor(hp - reduced, w)
} else {
sin_taylor(reduced, w)
};
if neg { -s } else { s }
}
pub(crate) fn sin_cos_fixed(v_w: W, w: u32) -> (W, W) {
let pi_w = pi(w);
let tau = pi_w + pi_w;
let hp = pi_w >> 1;
let qp = hp >> 1;
let q = round_to_nearest_int(div(v_w, tau, w), w);
let r = v_w - scale_by_k(tau, q);
let sin_neg = r < zero();
let abs_r = if sin_neg { -r } else { r };
let cos_neg = abs_r > hp; let reduced = if cos_neg { pi_w - abs_r } else { abs_r };
let s_abs = if reduced > qp {
cos_taylor(hp - reduced, w)
} else {
sin_taylor(reduced, w)
};
let one_w = one(w);
let s2 = mul(s_abs, s_abs, w);
let cos_abs = sqrt_fixed(one_w - s2, w);
let sin_result = if sin_neg { -s_abs } else { s_abs };
let cos_result = if cos_neg { -cos_abs } else { cos_abs };
(sin_result, cos_result)
}
pub(crate) fn cos_fixed(v_w: W, w: u32) -> W {
sin_fixed(half_pi(w) - v_w, w)
}
pub(crate) fn atan_fixed(v_w: W, w: u32) -> W {
let one_w = one(w);
let sign = v_w < zero();
let mut x = if sign { -v_w } else { v_w };
let mut add_half_pi = false;
if x > one_w {
x = div(one_w, x, w);
add_half_pi = true;
}
let halvings: u32 = if w < 60 {
5 } else if w < 110 {
6 } else {
7 };
let pow10_w = pow10_cached(w);
for _ in 0..halvings {
let x2 = mul_cached(x, x, pow10_w);
let denom = one_w + sqrt_fixed(one_w + x2, w);
x = div_cached(x, denom, pow10_w);
}
let mut result = atan_taylor(x, w) << halvings;
if add_half_pi {
result = half_pi(w) - result;
}
if sign { -result } else { result }
}
}
impl<const SCALE: u32> $Type<SCALE> {
#[inline]
#[must_use]
pub fn ln_strict(self) -> Self {
<Self as $crate::policy::ln::LnPolicy>::ln_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn ln_strict_agm(self) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::ln_agm: argument must be positive"));
}
let w_prime = SCALE + $core::GUARD + $core::guard_agm(SCALE);
let r = $core::ln_fixed_agm(
$core::to_work_w(raw, $core::GUARD + $core::guard_agm(SCALE)),
w_prime,
);
Self::from_bits($core::round_to_storage(r, w_prime, SCALE))
}
#[inline]
#[must_use]
pub fn exp_strict_agm(self) -> Self {
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ONE;
}
let raw_w = $core::to_work_w(raw, 0);
let k_lift = $core::exp_agm_k_lift_from_w(raw_w, SCALE);
let lift = $core::GUARD + $core::guard_agm(SCALE) + k_lift;
let w_prime = SCALE + lift;
let r = $core::exp_fixed_agm(
$core::to_work_w(raw, lift),
w_prime,
);
Self::from_bits($core::round_to_storage(r, w_prime, SCALE))
}
#[inline]
#[must_use]
pub fn log_strict(self, base: Self) -> Self {
let raw = self.to_bits();
let braw = base.to_bits();
let z = $crate::macros::wide_roots::wide_lit!($Storage, "0");
if raw <= z {
panic!(concat!(stringify!($Type), "::log: argument must be positive"));
}
if braw <= z {
panic!(concat!(stringify!($Type), "::log: base must be positive"));
}
let w = SCALE + $core::GUARD;
let ln_b = $core::ln_fixed($core::to_work(braw), w);
if ln_b == $core::zero() {
panic!(concat!(stringify!($Type), "::log: base must not equal 1"));
}
let r = $core::div($core::ln_fixed($core::to_work(raw), w), ln_b, w);
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[inline]
#[must_use]
pub fn log2_strict(self) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::log2: argument must be positive"));
}
let w = SCALE + $core::GUARD;
let r = $core::div($core::ln_fixed($core::to_work(raw), w), $core::ln2(w), w);
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[inline]
#[must_use]
pub fn log10_strict(self) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::log10: argument must be positive"));
}
let w = SCALE + $core::GUARD;
let r = $core::div($core::ln_fixed($core::to_work(raw), w), $core::ln10(w), w);
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[inline]
#[must_use]
pub fn exp_strict(self) -> Self {
<Self as $crate::policy::exp::ExpPolicy>::exp_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn exp2_strict(self) -> Self {
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ONE;
}
let w = SCALE + $core::GUARD;
let arg = $core::mul($core::to_work(raw), $core::ln2(w), w);
let r = $core::exp_fixed(arg, w);
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[inline]
#[must_use]
pub fn powf_strict(self, exp: Self) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ZERO;
}
if let ::core::option::Option::Some(n) = Self::powf_exp_as_small_int(exp) {
return self.powi(n);
}
let w = SCALE + $core::GUARD;
let ln_x = $core::ln_fixed($core::to_work(raw), w);
let y = $core::to_work(exp.to_bits());
let r = $core::exp_fixed($core::mul(y, ln_x, w), w);
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
const INT_POWF_FAST_PATH_THRESHOLD: i32 = 64;
#[inline]
fn powf_exp_as_small_int(exp: Self) -> ::core::option::Option<i32> {
let raw = exp.to_bits();
let mult = Self::multiplier();
let zero = $crate::macros::wide_roots::wide_lit!($Storage, "0");
if raw % mult != zero {
return ::core::option::Option::None;
}
let q = raw / mult;
let lo = $crate::macros::wide_roots::wide_lit!(
$Storage,
"-64"
);
let hi = $crate::macros::wide_roots::wide_lit!(
$Storage,
"64"
);
if q < lo || q > hi {
return ::core::option::Option::None;
}
let q_i128: i128 = $crate::wide_int::wide_cast::<$Storage, i128>(q);
::core::option::Option::Some(q_i128 as i32)
}
#[inline]
#[must_use]
pub fn sin_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::sin_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn cos_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::cos_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn sin_cos_strict(self) -> (Self, Self) {
let w = SCALE + $core::GUARD;
let (s, c) = $core::sin_cos_fixed($core::to_work(self.to_bits()), w);
(
Self::from_bits($core::round_to_storage(s, w, SCALE)),
Self::from_bits($core::round_to_storage(c, w, SCALE)),
)
}
#[inline]
#[must_use]
pub fn tan_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::tan_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn atan_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::atan_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn asin_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::asin_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn acos_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::acos_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn atan2_strict(self, other: Self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::atan2_impl(
self,
other,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn sinh_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::sinh_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn cosh_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::cosh_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn tanh_strict(self) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::tanh_impl(
self,
$crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
#[inline]
#[must_use]
pub fn sinh_cosh_strict(self) -> (Self, Self) {
let w = SCALE + $core::GUARD;
let v = $core::to_work(self.to_bits());
let ex = $core::exp_fixed(v, w);
let enx = $core::div($core::one(w), ex, w);
let sinh = (ex - enx) >> 1;
let cosh = (ex + enx) >> 1;
(
Self::from_bits($core::round_to_storage(sinh, w, SCALE)),
Self::from_bits($core::round_to_storage(cosh, w, SCALE)),
)
}
#[inline]
#[must_use]
pub fn asinh_strict(self) -> Self {
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ZERO;
}
let w = SCALE + $core::GUARD;
let one_w = $core::one(w);
let v = $core::to_work(raw);
let ax = if v < $core::zero() { -v } else { v };
let inner = if ax >= one_w {
let inv = $core::div(one_w, ax, w);
let root = $core::sqrt_fixed(one_w + $core::mul(inv, inv, w), w);
$core::ln_fixed(ax, w) + $core::ln_fixed(one_w + root, w)
} else {
let root = $core::sqrt_fixed($core::mul(ax, ax, w) + one_w, w);
$core::ln_fixed(ax + root, w)
};
let signed = if raw < $crate::macros::wide_roots::wide_lit!($Storage, "0") {
-inner
} else {
inner
};
Self::from_bits($core::round_to_storage(signed, w, SCALE))
}
#[inline]
#[must_use]
pub fn acosh_strict(self) -> Self {
let w = SCALE + $core::GUARD;
let one_w = $core::one(w);
let v = $core::to_work(self.to_bits());
if v < one_w {
panic!(concat!(stringify!($Type), "::acosh: argument must be >= 1"));
}
let two_w = one_w + one_w;
let inner = if v >= two_w {
let inv = $core::div(one_w, v, w);
let root = $core::sqrt_fixed(one_w - $core::mul(inv, inv, w), w);
$core::ln_fixed(v, w) + $core::ln_fixed(one_w + root, w)
} else {
let t = v - one_w;
let root = $core::sqrt_fixed($core::mul(t, t + two_w, w), w);
$core::log1p_fixed(t + root, w)
};
Self::from_bits($core::round_to_storage(inner, w, SCALE))
}
#[inline]
#[must_use]
pub fn atanh_strict(self) -> Self {
let w = SCALE + $core::GUARD;
let one_w = $core::one(w);
let v = $core::to_work(self.to_bits());
let ax = if v < $core::zero() { -v } else { v };
if ax >= one_w {
panic!(concat!(stringify!($Type), "::atanh: argument out of domain (-1, 1)"));
}
let r = ($core::ln_fixed(one_w + v, w) - $core::ln_fixed(one_w - v, w)) >> 1;
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[inline]
#[must_use]
pub fn to_degrees_strict(self) -> Self {
let w = SCALE + $core::GUARD;
let v = $core::to_work(self.to_bits());
debug_assert!(
$core::bit_length(v) + 8 < <$Work>::BITS,
concat!(stringify!($Type),
"::to_degrees: |self| * 180 overflows the working integer")
);
let r = $core::div(
v * $crate::macros::wide_roots::wide_lit!($Work, "180"),
$core::pi(w),
w,
);
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[inline]
#[must_use]
pub fn to_radians_strict(self) -> Self {
let w = SCALE + $core::GUARD;
let v = $core::to_work(self.to_bits());
let r = $core::mul(v, $core::pi(w), w)
/ $crate::macros::wide_roots::wide_lit!($Work, "180");
Self::from_bits($core::round_to_storage(r, w, SCALE))
}
#[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_strict_agm_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::ln_agm: argument must be positive"));
}
let w_prime = SCALE + $core::GUARD + $core::guard_agm(SCALE);
let r = $core::ln_fixed_agm(
$core::to_work_w(raw, $core::GUARD + $core::guard_agm(SCALE)),
w_prime,
);
Self::from_bits($core::round_to_storage_with(r, w_prime, SCALE, mode))
}
#[inline]
#[must_use]
pub fn exp_strict_agm_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ONE;
}
let raw_w = $core::to_work_w(raw, 0);
let k_lift = $core::exp_agm_k_lift_from_w(raw_w, SCALE);
let lift = $core::GUARD + $core::guard_agm(SCALE) + k_lift;
let w_prime = SCALE + lift;
let r = $core::exp_fixed_agm(
$core::to_work_w(raw, lift),
w_prime,
);
Self::from_bits($core::round_to_storage_with(r, w_prime, SCALE, mode))
}
#[inline]
#[must_use]
pub fn log_strict_with(self, base: Self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
let braw = base.to_bits();
let z = $crate::macros::wide_roots::wide_lit!($Storage, "0");
if raw <= z {
panic!(concat!(stringify!($Type), "::log: argument must be positive"));
}
if braw <= z {
panic!(concat!(stringify!($Type), "::log: base must be positive"));
}
let w0 = SCALE + $core::GUARD;
let ln_b0 = $core::ln_fixed($core::to_work(braw), w0);
if ln_b0 == $core::zero() {
panic!(concat!(stringify!($Type), "::log: base must not equal 1"));
}
{
let r0 = $core::div($core::ln_fixed($core::to_work(raw), w0), ln_b0, w0);
let k = $core::round_to_nearest_int(r0, w0);
if $core::log_is_exact_int(
$core::to_work_w(raw, 0), $core::to_work_w(braw, 0), SCALE, k,
) {
return Self::from_bits($core::exact_int_at_scale(k, SCALE));
}
}
Self::from_bits($core::round_to_storage_directed(
$core::GUARD, SCALE, mode, |guard| {
let w = SCALE + guard;
let ln_b = $core::ln_fixed($core::to_work_w(braw, guard), w);
$core::div($core::ln_fixed($core::to_work_w(raw, guard), w), ln_b, w)
},
))
}
#[inline]
#[must_use]
pub fn log2_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::log2: argument must be positive"));
}
{
let w0 = SCALE + $core::GUARD;
let r0 = $core::div($core::ln_fixed($core::to_work(raw), w0), $core::ln2(w0), w0);
let k = $core::round_to_nearest_int(r0, w0);
let base2 = $core::pow10_cached(SCALE) + $core::pow10_cached(SCALE); if $core::log_is_exact_int($core::to_work_w(raw, 0), base2, SCALE, k) {
return Self::from_bits($core::exact_int_at_scale(k, SCALE));
}
}
Self::from_bits($core::round_to_storage_directed(
$core::GUARD, SCALE, mode, |guard| {
let w = SCALE + guard;
$core::div($core::ln_fixed($core::to_work_w(raw, guard), w), $core::ln2(w), w)
},
))
}
#[inline]
#[must_use]
pub fn log10_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::log10: argument must be positive"));
}
{
let w0 = SCALE + $core::GUARD;
let r0 = $core::div($core::ln_fixed($core::to_work(raw), w0), $core::ln10(w0), w0);
let k = $core::round_to_nearest_int(r0, w0);
let base10 = $core::pow10_cached(SCALE + 1); if $core::log_is_exact_int($core::to_work_w(raw, 0), base10, SCALE, k) {
return Self::from_bits($core::exact_int_at_scale(k, SCALE));
}
}
Self::from_bits($core::round_to_storage_directed(
$core::GUARD, SCALE, mode, |guard| {
let w = SCALE + guard;
$core::div($core::ln_fixed($core::to_work_w(raw, guard), w), $core::ln10(w), w)
},
))
}
#[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 exp2_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ONE;
}
if let ::core::option::Option::Some(v) = $core::exp2_exact_pin(raw, SCALE) {
return Self::from_bits(v);
}
let k_lift = $core::exp2_result_int_digits(raw, SCALE);
let base_guard = $core::GUARD + k_lift;
Self::from_bits($core::round_to_storage_directed(
base_guard, SCALE, mode, |guard| {
let w = SCALE + guard;
let arg = $core::mul($core::to_work_w(raw, guard), $core::ln2(w), w);
$core::exp_fixed_wide(arg, w)
},
))
}
#[inline]
#[must_use]
pub fn powf_strict_with(self, exp: Self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ZERO;
}
if let ::core::option::Option::Some(n) = Self::powf_exp_as_small_int(exp) {
return self.powi(n);
}
{
let two = $crate::macros::wide_roots::wide_lit!($Storage, "2");
let mult = Self::multiplier();
if exp.to_bits() == mult / two {
return self.sqrt_strict_with(mode);
}
}
let eraw = exp.to_bits();
let k_lift = {
let w0 = SCALE + $core::GUARD;
let ln_x0 = $core::ln_fixed($core::to_work(raw), w0);
let arg0 = $core::mul($core::to_work(eraw), ln_x0, w0);
let arg_at_scale = $core::round_to_storage_with(arg0, w0, SCALE, $crate::support::rounding::RoundingMode::Trunc);
$core::exp_result_int_digits($core::to_work_w(arg_at_scale, 0), SCALE)
};
let base_guard = $core::GUARD + k_lift;
Self::from_bits($core::round_to_storage_directed(
base_guard, SCALE, mode, |guard| {
let w = SCALE + guard;
let ln_x = $core::ln_fixed($core::to_work_w(raw, guard), w);
let y = $core::to_work_w(eraw, guard);
$core::exp_fixed($core::mul(y, ln_x, w), w)
},
))
}
#[inline]
#[must_use]
pub fn sin_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::sin_impl(self, mode)
}
#[inline]
#[must_use]
pub fn cos_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::cos_impl(self, mode)
}
#[inline]
#[must_use]
pub fn tan_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::tan_impl(self, mode)
}
#[inline]
#[must_use]
pub fn atan_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
<Self as $crate::policy::trig::TrigPolicy>::atan_impl(self, mode)
}
#[inline]
#[must_use]
pub fn asin_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let w = SCALE + $core::GUARD;
let one_w = $core::one(w);
let v = $core::to_work(self.to_bits());
let abs_v = if v < $core::zero() { -v } else { v };
if abs_v > one_w {
panic!(concat!(stringify!($Type), "::asin: argument out of domain [-1, 1]"));
}
let half_w = one_w >> 1;
let r = if abs_v == one_w {
let hp = $core::half_pi(w);
if v < $core::zero() { -hp } else { hp }
} else if abs_v <= half_w {
let denom = $core::sqrt_fixed(one_w - $core::mul(v, v, w), w);
$core::atan_fixed($core::div(v, denom, w), w)
} else {
let inner = (one_w - abs_v) >> 1;
let inner_sqrt = $core::sqrt_fixed(inner, w);
let inner_denom = $core::sqrt_fixed(
one_w - $core::mul(inner_sqrt, inner_sqrt, w),
w,
);
let inner_asin = $core::atan_fixed(
$core::div(inner_sqrt, inner_denom, w),
w,
);
let result_abs = $core::half_pi(w) - inner_asin - inner_asin;
if v < $core::zero() { -result_abs } else { result_abs }
};
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn acos_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let w = SCALE + $core::GUARD;
let one_w = $core::one(w);
let v = $core::to_work(self.to_bits());
let abs_v = if v < $core::zero() { -v } else { v };
if abs_v > one_w {
panic!(concat!(stringify!($Type), "::acos: argument out of domain [-1, 1]"));
}
let half_w = one_w >> 1;
let asin_w = if abs_v == one_w {
let hp = $core::half_pi(w);
if v < $core::zero() { -hp } else { hp }
} else if abs_v <= half_w {
let denom = $core::sqrt_fixed(one_w - $core::mul(v, v, w), w);
$core::atan_fixed($core::div(v, denom, w), w)
} else {
let inner = (one_w - abs_v) >> 1;
let inner_sqrt = $core::sqrt_fixed(inner, w);
let inner_denom = $core::sqrt_fixed(
one_w - $core::mul(inner_sqrt, inner_sqrt, w),
w,
);
let inner_asin = $core::atan_fixed(
$core::div(inner_sqrt, inner_denom, w),
w,
);
let result_abs = $core::half_pi(w) - inner_asin - inner_asin;
if v < $core::zero() { -result_abs } else { result_abs }
};
let r = $core::half_pi(w) - asin_w;
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn atan2_strict_with(self, other: Self, mode: $crate::support::rounding::RoundingMode) -> Self {
let w = SCALE + $core::GUARD;
let z = $crate::macros::wide_roots::wide_lit!($Storage, "0");
let yraw = self.to_bits();
let xraw = other.to_bits();
let r = if xraw == z {
if yraw > z {
$core::half_pi(w)
} else if yraw < z {
-$core::half_pi(w)
} else {
$core::zero()
}
} else {
let y = $core::to_work(yraw);
let x = $core::to_work(xraw);
let zero_w = $core::zero();
let abs_y = if y < zero_w { -y } else { y };
let abs_x = if x < zero_w { -x } else { x };
let base = if abs_x >= abs_y {
$core::atan_fixed($core::div(y, x, w), w)
} else {
let inv = $core::atan_fixed($core::div(x, y, w), w);
let hp = $core::half_pi(w);
let same_sign = (y < zero_w) == (x < zero_w);
if same_sign { hp - inv } else { -hp - inv }
};
if xraw > z {
base
} else if yraw >= z {
base + $core::pi(w)
} else {
base - $core::pi(w)
}
};
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn sinh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
let szero = <$Storage>::from_i128(0);
if raw != szero {
let thresh_exp = SCALE - (SCALE + 2) / 3;
let thresh = <$Storage>::from_i128(10).pow(thresh_exp);
if raw.abs() <= thresh {
return Self::from_bits(
$crate::support::rounding::tiny_odd_expanding_directed(
raw,
szero,
<$Storage>::from_i128(1),
mode,
),
);
}
}
let neg = raw < <$Storage>::from_i128(0);
let k_lift = $core::exp_result_int_digits($core::to_work_w(raw, 0), SCALE);
let base_guard = $core::GUARD + k_lift;
Self::from_bits($core::round_to_storage_directed(
base_guard, SCALE, mode, |guard| {
let w = SCALE + guard;
let v = $core::to_work_w(raw, guard);
let av = if v < $core::zero() { -v } else { v };
let sh = $core::sinh_pos_wide(av, w);
if neg { -sh } else { sh }
},
))
}
#[inline]
#[must_use]
pub fn cosh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
let k_lift = $core::exp_result_int_digits($core::to_work_w(raw, 0), SCALE);
let base_guard = $core::GUARD + k_lift;
Self::from_bits($core::round_to_storage_directed(
base_guard, SCALE, mode, |guard| {
let w = SCALE + guard;
let v = $core::to_work_w(raw, guard);
let av = if v < $core::zero() { -v } else { v };
$core::cosh_pos_wide(av, w)
},
))
}
#[inline]
#[must_use]
pub fn tanh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
let zero = <$Storage>::from_i128(0);
if raw != zero {
let thresh_exp = SCALE - (SCALE + 2) / 3;
let thresh = <$Storage>::from_i128(10).pow(thresh_exp);
if raw.abs() <= thresh {
return Self::from_bits(
$crate::support::rounding::tiny_odd_compressing_directed(
raw,
zero,
<$Storage>::from_i128(1),
mode,
),
);
}
}
let neg = raw < zero;
let k_lift = $core::exp_result_int_digits($core::to_work_w(raw, 0), SCALE);
let base_guard = $core::GUARD + k_lift;
Self::from_bits($core::round_to_storage_directed(
base_guard, SCALE, mode, |guard| {
let w = SCALE + guard;
let v = $core::to_work_w(raw, guard);
let av = if v < $core::zero() { -v } else { v };
let th = $core::tanh_pos_wide(av, w);
if neg { -th } else { th }
},
))
}
#[inline]
#[must_use]
pub fn asinh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ZERO;
}
let neg = raw < $crate::macros::wide_roots::wide_lit!($Storage, "0");
Self::from_bits($core::round_to_storage_directed(
$core::GUARD, SCALE, mode, |guard| {
let w = SCALE + guard;
let one_w = $core::one(w);
let v = $core::to_work_w(raw, guard);
let ax = if v < $core::zero() { -v } else { v };
let inner = if ax >= one_w {
let inv = $core::div(one_w, ax, w);
let root = $core::sqrt_fixed(one_w + $core::mul(inv, inv, w), w);
$core::ln_fixed(ax, w) + $core::ln_fixed(one_w + root, w)
} else {
let root = $core::sqrt_fixed($core::mul(ax, ax, w) + one_w, w);
$core::ln_fixed(ax + root, w)
};
if neg { -inner } else { inner }
},
))
}
#[inline]
#[must_use]
pub fn acosh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
{
let w0 = SCALE + $core::GUARD;
if $core::to_work(raw) < $core::one(w0) {
panic!(concat!(stringify!($Type), "::acosh: argument must be >= 1"));
}
}
Self::from_bits($core::round_to_storage_directed_near_special(
$core::GUARD, SCALE, mode, |guard| {
let w = SCALE + guard;
let one_w = $core::one(w);
let v = $core::to_work_w(raw, guard);
let two_w = one_w + one_w;
if v >= two_w {
let inv = $core::div(one_w, v, w);
let root = $core::sqrt_fixed(one_w - $core::mul(inv, inv, w), w);
$core::ln_fixed(v, w) + $core::ln_fixed(one_w + root, w)
} else {
let t = v - one_w;
let root = $core::sqrt_fixed($core::mul(t, t + two_w, w), w);
$core::log1p_fixed(t + root, w)
}
},
))
}
#[inline]
#[must_use]
pub fn atanh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let raw = self.to_bits();
{
let w0 = SCALE + $core::GUARD;
let v0 = $core::to_work(raw);
let ax0 = if v0 < $core::zero() { -v0 } else { v0 };
if ax0 >= $core::one(w0) {
panic!(concat!(stringify!($Type), "::atanh: argument out of domain (-1, 1)"));
}
}
Self::from_bits($core::round_to_storage_directed_near_special(
$core::GUARD, SCALE, mode, |guard| {
let w = SCALE + guard;
let one_w = $core::one(w);
let v = $core::to_work_w(raw, guard);
($core::ln_fixed(one_w + v, w) - $core::ln_fixed(one_w - v, w)) >> 1
},
))
}
#[inline]
#[must_use]
pub fn to_degrees_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let w = SCALE + $core::GUARD;
let v = $core::to_work(self.to_bits());
debug_assert!(
$core::bit_length(v) + 8 < <$Work>::BITS,
concat!(stringify!($Type),
"::to_degrees: |self| * 180 overflows the working integer")
);
let r = $core::div(
v * $crate::macros::wide_roots::wide_lit!($Work, "180"),
$core::pi(w),
w,
);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn to_radians_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> Self {
let w = SCALE + $core::GUARD;
let v = $core::to_work(self.to_bits());
let r = $core::mul(v, $core::pi(w), w)
/ $crate::macros::wide_roots::wide_lit!($Work, "180");
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn sin_cos_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> (Self, Self) {
let w = SCALE + $core::GUARD;
let (s, c) = $core::sin_cos_fixed($core::to_work(self.to_bits()), w);
(
Self::from_bits($core::round_to_storage_with(s, w, SCALE, mode)),
Self::from_bits($core::round_to_storage_with(c, w, SCALE, mode)),
)
}
#[inline]
#[must_use]
pub fn sinh_cosh_strict_with(self, mode: $crate::support::rounding::RoundingMode) -> (Self, Self) {
let w = SCALE + $core::GUARD;
let v = $core::to_work(self.to_bits());
let ex = $core::exp_fixed(v, w);
let enx = $core::div($core::one(w), ex, w);
let sinh = (ex - enx) >> 1;
let cosh = (ex + enx) >> 1;
(
Self::from_bits($core::round_to_storage_with(sinh, w, SCALE, mode)),
Self::from_bits($core::round_to_storage_with(cosh, w, SCALE, 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 == $core::GUARD {
return self.ln_strict_with(mode);
}
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::ln: argument must be positive"));
}
let w = SCALE + working_digits;
let r = $core::ln_fixed($core::to_work_w(raw, working_digits), w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, 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 == $core::GUARD {
return self.log_strict_with(base, mode);
}
let raw = self.to_bits();
let braw = base.to_bits();
let z = $crate::macros::wide_roots::wide_lit!($Storage, "0");
if raw <= z {
panic!(concat!(stringify!($Type), "::log: argument must be positive"));
}
if braw <= z {
panic!(concat!(stringify!($Type), "::log: base must be positive"));
}
let w = SCALE + working_digits;
let ln_b = $core::ln_fixed($core::to_work_w(braw, working_digits), w);
if ln_b == $core::zero() {
panic!(concat!(stringify!($Type), "::log: base must not equal 1"));
}
let r = $core::div(
$core::ln_fixed($core::to_work_w(raw, working_digits), w),
ln_b,
w,
);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, 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 == $core::GUARD {
return self.log2_strict_with(mode);
}
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::log2: argument must be positive"));
}
let w = SCALE + working_digits;
let r = $core::div(
$core::ln_fixed($core::to_work_w(raw, working_digits), w),
$core::ln2(w),
w,
);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, 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 == $core::GUARD {
return self.log10_strict_with(mode);
}
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
panic!(concat!(stringify!($Type), "::log10: argument must be positive"));
}
let w = SCALE + working_digits;
let r = $core::div(
$core::ln_fixed($core::to_work_w(raw, working_digits), w),
$core::ln10(w),
w,
);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, 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 == $core::GUARD {
return self.exp_strict_with(mode);
}
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ONE;
}
let w = SCALE + working_digits;
let r = $core::exp_fixed($core::to_work_w(raw, working_digits), w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, 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 == $core::GUARD {
return self.exp2_strict_with(mode);
}
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ONE;
}
let w = SCALE + working_digits;
let arg = $core::mul($core::to_work_w(raw, working_digits), $core::ln2(w), w);
let r = $core::exp_fixed(arg, w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn powf_approx(self, exp: Self, working_digits: u32) -> Self {
self.powf_approx_with(exp, working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn powf_approx_with(
self,
exp: Self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.powf_strict_with(exp, mode);
}
let raw = self.to_bits();
if raw <= $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ZERO;
}
let w = SCALE + working_digits;
let ln_x = $core::ln_fixed($core::to_work_w(raw, working_digits), w);
let y = $core::to_work_w(exp.to_bits(), working_digits);
let r = $core::exp_fixed($core::mul(y, ln_x, w), w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn sin_approx(self, working_digits: u32) -> Self {
self.sin_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn sin_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.sin_strict_with(mode);
}
let w = SCALE + working_digits;
let r = $core::sin_fixed($core::to_work_w(self.to_bits(), working_digits), w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn cos_approx(self, working_digits: u32) -> Self {
self.cos_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn cos_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.cos_strict_with(mode);
}
let w = SCALE + working_digits;
let arg = $core::to_work_w(self.to_bits(), working_digits) + $core::half_pi(w);
let r = $core::sin_fixed(arg, w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn sin_cos_approx(self, working_digits: u32) -> (Self, Self) {
self.sin_cos_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn sin_cos_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> (Self, Self) {
if working_digits == $core::GUARD {
return self.sin_cos_strict_with(mode);
}
let w = SCALE + working_digits;
let (s, c) = $core::sin_cos_fixed(
$core::to_work_w(self.to_bits(), working_digits),
w,
);
(
Self::from_bits($core::round_to_storage_with(s, w, SCALE, mode)),
Self::from_bits($core::round_to_storage_with(c, w, SCALE, mode)),
)
}
#[inline]
#[must_use]
pub fn tan_approx(self, working_digits: u32) -> Self {
self.tan_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn tan_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.tan_strict_with(mode);
}
let w = SCALE + working_digits;
let (sin_w, cos_w) = $core::sin_cos_fixed(
$core::to_work_w(self.to_bits(), working_digits),
w,
);
if cos_w == $core::zero() {
panic!(concat!(
stringify!($Type),
"::tan: cosine is zero (argument is an odd multiple of pi/2)"
));
}
let r = $core::div(sin_w, cos_w, w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn atan_approx(self, working_digits: u32) -> Self {
self.atan_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn atan_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.atan_strict_with(mode);
}
let w = SCALE + working_digits;
let r = $core::atan_fixed($core::to_work_w(self.to_bits(), working_digits), w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn asin_approx(self, working_digits: u32) -> Self {
self.asin_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn asin_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.asin_strict_with(mode);
}
let w = SCALE + working_digits;
let one_w = $core::one(w);
let v = $core::to_work_w(self.to_bits(), working_digits);
let abs_v = if v < $core::zero() { -v } else { v };
if abs_v > one_w {
panic!(concat!(stringify!($Type), "::asin: argument out of domain [-1, 1]"));
}
let half_w = one_w >> 1;
let r = if abs_v == one_w {
let hp = $core::half_pi(w);
if v < $core::zero() { -hp } else { hp }
} else if abs_v <= half_w {
let denom = $core::sqrt_fixed(one_w - $core::mul(v, v, w), w);
$core::atan_fixed($core::div(v, denom, w), w)
} else {
let inner = (one_w - abs_v) >> 1;
let inner_sqrt = $core::sqrt_fixed(inner, w);
let inner_denom = $core::sqrt_fixed(
one_w - $core::mul(inner_sqrt, inner_sqrt, w),
w,
);
let inner_asin = $core::atan_fixed(
$core::div(inner_sqrt, inner_denom, w),
w,
);
let result_abs = $core::half_pi(w) - inner_asin - inner_asin;
if v < $core::zero() { -result_abs } else { result_abs }
};
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn acos_approx(self, working_digits: u32) -> Self {
self.acos_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn acos_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.acos_strict_with(mode);
}
let w = SCALE + working_digits;
let one_w = $core::one(w);
let v = $core::to_work_w(self.to_bits(), working_digits);
let abs_v = if v < $core::zero() { -v } else { v };
if abs_v > one_w {
panic!(concat!(stringify!($Type), "::acos: argument out of domain [-1, 1]"));
}
let half_w = one_w >> 1;
let asin_w = if abs_v == one_w {
let hp = $core::half_pi(w);
if v < $core::zero() { -hp } else { hp }
} else if abs_v <= half_w {
let denom = $core::sqrt_fixed(one_w - $core::mul(v, v, w), w);
$core::atan_fixed($core::div(v, denom, w), w)
} else {
let inner = (one_w - abs_v) >> 1;
let inner_sqrt = $core::sqrt_fixed(inner, w);
let inner_denom = $core::sqrt_fixed(
one_w - $core::mul(inner_sqrt, inner_sqrt, w),
w,
);
let inner_asin = $core::atan_fixed(
$core::div(inner_sqrt, inner_denom, w),
w,
);
let result_abs = $core::half_pi(w) - inner_asin - inner_asin;
if v < $core::zero() { -result_abs } else { result_abs }
};
let r = $core::half_pi(w) - asin_w;
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn atan2_approx(self, other: Self, working_digits: u32) -> Self {
self.atan2_approx_with(other, working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn atan2_approx_with(
self,
other: Self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.atan2_strict_with(other, mode);
}
let w = SCALE + working_digits;
let z = $crate::macros::wide_roots::wide_lit!($Storage, "0");
let yraw = self.to_bits();
let xraw = other.to_bits();
let r = if xraw == z {
if yraw > z {
$core::half_pi(w)
} else if yraw < z {
-$core::half_pi(w)
} else {
$core::zero()
}
} else {
let y = $core::to_work_w(yraw, working_digits);
let x = $core::to_work_w(xraw, working_digits);
let base = $core::atan_fixed($core::div(y, x, w), w);
if xraw > z {
base
} else if yraw >= z {
base + $core::pi(w)
} else {
base - $core::pi(w)
}
};
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn sinh_approx(self, working_digits: u32) -> Self {
self.sinh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn sinh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.sinh_strict_with(mode);
}
let w = SCALE + working_digits;
let v = $core::to_work_w(self.to_bits(), working_digits);
let ex = $core::exp_fixed(v, w);
let enx = $core::div($core::one(w), ex, w);
let r = (ex - enx) >> 1;
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn cosh_approx(self, working_digits: u32) -> Self {
self.cosh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn cosh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.cosh_strict_with(mode);
}
let w = SCALE + working_digits;
let v = $core::to_work_w(self.to_bits(), working_digits);
let ex = $core::exp_fixed(v, w);
let enx = $core::div($core::one(w), ex, w);
let r = (ex + enx) >> 1;
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn tanh_approx(self, working_digits: u32) -> Self {
self.tanh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn tanh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.tanh_strict_with(mode);
}
let w = SCALE + working_digits;
let v = $core::to_work_w(self.to_bits(), working_digits);
let ex = $core::exp_fixed(v, w);
let enx = $core::div($core::one(w), ex, w);
let r = $core::div(ex - enx, ex + enx, w);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn sinh_cosh_approx(self, working_digits: u32) -> (Self, Self) {
self.sinh_cosh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn sinh_cosh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> (Self, Self) {
if working_digits == $core::GUARD {
return self.sinh_cosh_strict_with(mode);
}
let w = SCALE + working_digits;
let v = $core::to_work_w(self.to_bits(), working_digits);
let ex = $core::exp_fixed(v, w);
let enx = $core::div($core::one(w), ex, w);
let sinh = (ex - enx) >> 1;
let cosh = (ex + enx) >> 1;
(
Self::from_bits($core::round_to_storage_with(sinh, w, SCALE, mode)),
Self::from_bits($core::round_to_storage_with(cosh, w, SCALE, mode)),
)
}
#[inline]
#[must_use]
pub fn asinh_approx(self, working_digits: u32) -> Self {
self.asinh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn asinh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.asinh_strict_with(mode);
}
let raw = self.to_bits();
if raw == $crate::macros::wide_roots::wide_lit!($Storage, "0") {
return Self::ZERO;
}
let w = SCALE + working_digits;
let one_w = $core::one(w);
let v = $core::to_work_w(raw, working_digits);
let ax = if v < $core::zero() { -v } else { v };
let inner = if ax >= one_w {
let inv = $core::div(one_w, ax, w);
let root = $core::sqrt_fixed(one_w + $core::mul(inv, inv, w), w);
$core::ln_fixed(ax, w) + $core::ln_fixed(one_w + root, w)
} else {
let root = $core::sqrt_fixed($core::mul(ax, ax, w) + one_w, w);
$core::ln_fixed(ax + root, w)
};
let signed = if raw < $crate::macros::wide_roots::wide_lit!($Storage, "0") {
-inner
} else {
inner
};
Self::from_bits($core::round_to_storage_with(signed, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn acosh_approx(self, working_digits: u32) -> Self {
self.acosh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn acosh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.acosh_strict_with(mode);
}
let w = SCALE + working_digits;
let one_w = $core::one(w);
let v = $core::to_work_w(self.to_bits(), working_digits);
if v < one_w {
panic!(concat!(stringify!($Type), "::acosh: argument must be >= 1"));
}
let two_w = one_w + one_w;
let inner = if v >= two_w {
let inv = $core::div(one_w, v, w);
let root = $core::sqrt_fixed(one_w - $core::mul(inv, inv, w), w);
$core::ln_fixed(v, w) + $core::ln_fixed(one_w + root, w)
} else {
let t = v - one_w;
let root = $core::sqrt_fixed($core::mul(t, t + two_w, w), w);
$core::log1p_fixed(t + root, w)
};
Self::from_bits($core::round_to_storage_with(inner, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn atanh_approx(self, working_digits: u32) -> Self {
self.atanh_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn atanh_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.atanh_strict_with(mode);
}
let w = SCALE + working_digits;
let one_w = $core::one(w);
let v = $core::to_work_w(self.to_bits(), working_digits);
let ax = if v < $core::zero() { -v } else { v };
if ax >= one_w {
panic!(concat!(stringify!($Type), "::atanh: argument out of domain (-1, 1)"));
}
let r = ($core::ln_fixed(one_w + v, w) - $core::ln_fixed(one_w - v, w)) >> 1;
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn to_degrees_approx(self, working_digits: u32) -> Self {
self.to_degrees_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn to_degrees_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.to_degrees_strict_with(mode);
}
let w = SCALE + working_digits;
let v = $core::to_work_w(self.to_bits(), working_digits);
debug_assert!(
$core::bit_length(v) + 8 < <$Work>::BITS,
concat!(stringify!($Type),
"::to_degrees: |self| * 180 overflows the working integer")
);
let r = $core::div(
v * $crate::macros::wide_roots::wide_lit!($Work, "180"),
$core::pi(w),
w,
);
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
#[inline]
#[must_use]
pub fn to_radians_approx(self, working_digits: u32) -> Self {
self.to_radians_approx_with(working_digits, $crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
#[must_use]
pub fn to_radians_approx_with(
self,
working_digits: u32,
mode: $crate::support::rounding::RoundingMode,
) -> Self {
if working_digits == $core::GUARD {
return self.to_radians_strict_with(mode);
}
let w = SCALE + working_digits;
let v = $core::to_work_w(self.to_bits(), working_digits);
let r = $core::mul(v, $core::pi(w), w)
/ $crate::macros::wide_roots::wide_lit!($Work, "180");
Self::from_bits($core::round_to_storage_with(r, w, SCALE, mode))
}
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
impl<const SCALE: u32> $Type<SCALE> {
#[inline]
#[must_use]
pub fn ln(self) -> Self {
self.ln_strict()
}
#[inline]
#[must_use]
pub fn log(self, base: Self) -> Self {
self.log_strict(base)
}
#[inline]
#[must_use]
pub fn log2(self) -> Self {
self.log2_strict()
}
#[inline]
#[must_use]
pub fn log10(self) -> Self {
self.log10_strict()
}
#[inline]
#[must_use]
pub fn exp(self) -> Self {
self.exp_strict()
}
#[inline]
#[must_use]
pub fn exp2(self) -> Self {
self.exp2_strict()
}
#[inline]
#[must_use]
pub fn powf(self, exp: Self) -> Self {
self.powf_strict(exp)
}
#[inline]
#[must_use]
pub fn sin(self) -> Self {
self.sin_strict()
}
#[inline]
#[must_use]
pub fn cos(self) -> Self {
self.cos_strict()
}
#[inline]
#[must_use]
pub fn tan(self) -> Self {
self.tan_strict()
}
#[inline]
#[must_use]
pub fn asin(self) -> Self {
self.asin_strict()
}
#[inline]
#[must_use]
pub fn acos(self) -> Self {
self.acos_strict()
}
#[inline]
#[must_use]
pub fn atan(self) -> Self {
self.atan_strict()
}
#[inline]
#[must_use]
pub fn atan2(self, other: Self) -> Self {
self.atan2_strict(other)
}
#[inline]
#[must_use]
pub fn sinh(self) -> Self {
self.sinh_strict()
}
#[inline]
#[must_use]
pub fn cosh(self) -> Self {
self.cosh_strict()
}
#[inline]
#[must_use]
pub fn tanh(self) -> Self {
self.tanh_strict()
}
#[inline]
#[must_use]
pub fn asinh(self) -> Self {
self.asinh_strict()
}
#[inline]
#[must_use]
pub fn acosh(self) -> Self {
self.acosh_strict()
}
#[inline]
#[must_use]
pub fn atanh(self) -> Self {
self.atanh_strict()
}
#[inline]
#[must_use]
pub fn to_degrees(self) -> Self {
self.to_degrees_strict()
}
#[inline]
#[must_use]
pub fn to_radians(self) -> Self {
self.to_radians_strict()
}
}
};
}
pub(crate) use decl_wide_transcendental;
#[cfg(all(test, not(feature = "fast")))]
mod tests {
use crate::{D38, D76, D153, D307};
#[test]
fn wide_transcendentals_match_d38() {
let positives = [1i64, 250_000, 500_000, 1_000_000, 2_718_282, 7_500_000];
let unit_range = [-900_000i64, -250_000, 1, 250_000, 900_000];
let all = [-3_000_000i64, -500_000, 1, 500_000, 1_500_000, 4_000_000];
fn agree(label: &str, ctx: i64, wide: i128, d38: i128) {
assert!(
(wide - d38).abs() <= 2,
"{label} mismatch at {ctx}: wide {wide} vs d38 {d38}"
);
}
for raw in positives {
let n = D38::<6>::from_bits(raw as i128);
let w = D76::<6>::from_bits(crate::wide_int::wide_cast::<i128, crate::wide_int::I256>(raw as i128));
agree("ln", raw, w.ln_strict().to_bits().resize::<i128>(), n.ln_strict().to_bits());
agree("log2", raw, w.log2_strict().to_bits().resize::<i128>(), n.log2_strict().to_bits());
agree("log10", raw, w.log10_strict().to_bits().resize::<i128>(), n.log10_strict().to_bits());
}
for raw in all {
let n = D38::<6>::from_bits(raw as i128);
let w = D76::<6>::from_bits(crate::wide_int::wide_cast::<i128, crate::wide_int::I256>(raw as i128));
agree("exp", raw, w.exp_strict().to_bits().resize::<i128>(), n.exp_strict().to_bits());
agree("sin", raw, w.sin_strict().to_bits().resize::<i128>(), n.sin_strict().to_bits());
agree("cos", raw, w.cos_strict().to_bits().resize::<i128>(), n.cos_strict().to_bits());
agree("atan", raw, w.atan_strict().to_bits().resize::<i128>(), n.atan_strict().to_bits());
agree("sinh", raw, w.sinh_strict().to_bits().resize::<i128>(), n.sinh_strict().to_bits());
agree("cosh", raw, w.cosh_strict().to_bits().resize::<i128>(), n.cosh_strict().to_bits());
agree("tanh", raw, w.tanh_strict().to_bits().resize::<i128>(), n.tanh_strict().to_bits());
}
for raw in unit_range {
let n = D38::<6>::from_bits(raw as i128);
let w = D76::<6>::from_bits(crate::wide_int::wide_cast::<i128, crate::wide_int::I256>(raw as i128));
agree("asin", raw, w.asin_strict().to_bits().resize::<i128>(), n.asin_strict().to_bits());
agree("acos", raw, w.acos_strict().to_bits().resize::<i128>(), n.acos_strict().to_bits());
agree("atanh", raw, w.atanh_strict().to_bits().resize::<i128>(), n.atanh_strict().to_bits());
}
}
#[test]
fn wide_transcendental_identities() {
assert_eq!(D76::<6>::ONE.ln_strict(), D76::<6>::ZERO);
assert_eq!(D76::<6>::ZERO.exp_strict(), D76::<6>::ONE);
assert_eq!(D76::<6>::ZERO.sin_strict(), D76::<6>::ZERO);
assert_eq!(D76::<6>::ZERO.sinh_strict(), D76::<6>::ZERO);
assert_eq!(D76::<6>::ZERO.atan_strict(), D76::<6>::ZERO);
assert_eq!(D153::<6>::ONE.ln_strict(), D153::<6>::ZERO);
assert_eq!(D153::<6>::ZERO.exp_strict(), D153::<6>::ONE);
assert_eq!(D153::<6>::ZERO.cos_strict(), D153::<6>::ONE);
assert_eq!(D307::<6>::ONE.ln_strict(), D307::<6>::ZERO);
assert_eq!(D307::<6>::ZERO.exp_strict(), D307::<6>::ONE);
assert_eq!(D307::<6>::ZERO.cosh_strict(), D307::<6>::ONE);
}
#[test]
fn wide_agm_matches_taylor_at_storage_scale() {
let positives = [1i64, 250_000, 500_000, 1_000_000, 2_718_282, 7_500_000];
let all = [-3_000_000i64, -500_000, 1, 500_000, 1_500_000, 4_000_000];
fn agree(label: &str, ctx: i64, agm: i128, taylor: i128) {
assert!(
(agm - taylor).abs() <= 2,
"{label} AGM-vs-Taylor mismatch at {ctx}: agm {agm} vs taylor {taylor}"
);
}
for raw in positives {
let w = D76::<6>::from_bits(
crate::wide_int::wide_cast::<i128, crate::wide_int::I256>(raw as i128),
);
agree(
"ln",
raw,
w.ln_strict_agm().to_bits().resize::<i128>(),
w.ln_strict().to_bits().resize::<i128>(),
);
}
for raw in all {
let w = D76::<6>::from_bits(
crate::wide_int::wide_cast::<i128, crate::wide_int::I256>(raw as i128),
);
agree(
"exp",
raw,
w.exp_strict_agm().to_bits().resize::<i128>(),
w.exp_strict().to_bits().resize::<i128>(),
);
}
}
#[test]
fn wide_agm_identity_points() {
assert_eq!(D76::<6>::ONE.ln_strict_agm(), D76::<6>::ZERO);
assert_eq!(D76::<6>::ZERO.exp_strict_agm(), D76::<6>::ONE);
assert_eq!(D153::<6>::ONE.ln_strict_agm(), D153::<6>::ZERO);
assert_eq!(D153::<6>::ZERO.exp_strict_agm(), D153::<6>::ONE);
assert_eq!(D307::<6>::ONE.ln_strict_agm(), D307::<6>::ZERO);
assert_eq!(D307::<6>::ZERO.exp_strict_agm(), D307::<6>::ONE);
}
#[test]
fn wide_strict_with_honours_mode() {
use crate::support::rounding::RoundingMode;
let n = D76::<6>::ONE;
let hte = n.exp_strict_with(RoundingMode::HalfToEven);
let trunc = n.exp_strict_with(RoundingMode::Trunc);
assert!(
hte.to_bits().resize::<i128>() - trunc.to_bits().resize::<i128>() == 1
|| hte.to_bits().resize::<i128>() - trunc.to_bits().resize::<i128>() == 0,
"exp(1) HTE vs Trunc: hte={}, trunc={}",
hte,
trunc,
);
if !(cfg!(feature = "rounding-half-away-from-zero")
|| cfg!(feature = "rounding-half-toward-zero")
|| cfg!(feature = "rounding-trunc")
|| cfg!(feature = "rounding-floor")
|| cfg!(feature = "rounding-ceiling"))
{
assert_eq!(hte, n.exp_strict());
}
}
#[test]
fn wide_agm_moderate_scale_round_trip() {
let x = D76::<20>::from_int(3);
let back = x.ln_strict_agm().exp_strict_agm();
let delta = (back.to_bits().resize::<i128>() - x.to_bits().resize::<i128>()).abs();
assert!(delta <= 8, "AGM exp(ln(3)) at D76<20> delta {delta}");
let y = D153::<20>::from_int(2);
let back = y.exp_strict_agm().ln_strict_agm();
let delta = (back.to_bits().resize::<i128>() - y.to_bits().resize::<i128>()).abs();
assert!(delta <= 8, "AGM ln(exp(2)) at D153<20> delta {delta}");
}
#[test]
fn wide_only_scale_round_trips() {
let x = D76::<50>::from_int(3);
let back = x.ln_strict().exp_strict();
let delta = (back.to_bits().resize::<i128>() - x.to_bits().resize::<i128>()).abs();
assert!(delta <= 8, "exp(ln(3)) at D76<50> delta {delta}");
let y = D307::<150>::from_int(2);
let back = y.exp_strict().ln_strict();
let delta = (back.to_bits().resize::<i128>() - y.to_bits().resize::<i128>()).abs();
assert!(delta <= 8, "ln(exp(2)) at D307<150> delta {delta}");
}
}