use crate::algos::exp::fixed_d38::exp_fixed;
use crate::algos::ln::fixed_d38::{STRICT_GUARD, ln_fixed};
use crate::types::consts::DecimalConstants;
use crate::types::widths::D38;
use crate::algos::fixed_d38::Fixed;
use crate::support::rounding::RoundingMode;
#[inline]
pub(crate) const fn small_x_linear_threshold<const SCALE: u32>() -> i128 {
let thresh_exp = SCALE.saturating_sub((SCALE + 2) / 3);
10_i128.pow(thresh_exp)
}
pub(crate) fn wide_pi(w: u32) -> Fixed {
debug_assert!(w <= 75, "wide_pi: working scale {w} exceeds embedded 75-digit π");
let words = crate::types::consts::PI_RAW.0;
let pi_at_75 = Fixed {
negative: false,
mag: [
(words[0] as u128) | ((words[1] as u128) << 64),
(words[2] as u128) | ((words[3] as u128) << 64),
],
};
if w == 75 {
pi_at_75
} else {
pi_at_75.rescale_down(75, w)
}
}
fn wide_tau(w: u32) -> Fixed {
wide_pi(w).double()
}
pub(crate) fn wide_half_pi(w: u32) -> Fixed {
wide_pi(w).halve()
}
pub(crate) fn to_fixed(raw: i128) -> Fixed {
to_fixed_w(raw, STRICT_GUARD)
}
pub(crate) fn to_fixed_w(raw: i128, working_digits: u32) -> Fixed {
let m = Fixed::from_u128_mag(raw.unsigned_abs(), false)
.mul_u128(10u128.pow(working_digits));
if raw < 0 {
m.neg()
} else {
m
}
}
pub(crate) fn atan2_kernel(
y: Fixed,
x: Fixed,
y_raw: i128,
w: u32,
) -> Fixed {
if x.is_zero() {
return if y_raw > 0 {
wide_half_pi(w)
} else if y_raw < 0 {
wide_half_pi(w).neg()
} else {
Fixed::ZERO
};
}
let abs_y_ge_abs_x = y.ge_mag(x);
let base = if !abs_y_ge_abs_x {
atan_fixed(y.div(x, w), w)
} else {
let inv = atan_fixed(x.div(y, w), w);
let hp = wide_half_pi(w);
let same_sign = y.negative == x.negative;
if same_sign { hp.sub(inv) } else { hp.neg().sub(inv) }
};
if !x.negative {
base
} else if !y.negative {
base.add(wide_pi(w))
} else {
base.sub(wide_pi(w))
}
}
fn sin_taylor(r: Fixed, w: u32) -> Fixed {
let r2 = r.mul(r, w);
let mut sum = r;
let mut term = r; let mut k: u128 = 1;
loop {
term = term.mul(r2, w).div_small((2 * k) * (2 * k + 1));
if term.is_zero() {
break;
}
if k % 2 == 1 {
sum = sum.sub(term);
} else {
sum = sum.add(term);
}
k += 1;
if k > 200 {
break;
}
}
sum
}
pub(crate) fn sin_fixed(v_w: Fixed, w: u32) -> Fixed {
let tau = wide_tau(w);
let pi = wide_pi(w);
let half_pi = wide_half_pi(w);
let q = v_w.div(tau, w).round_to_nearest_int(w);
let q_tau = if q >= 0 {
tau.mul_u128(q as u128)
} else {
tau.mul_u128((-q) as u128).neg()
};
let r = v_w.sub(q_tau);
let sign = r.negative;
let abs_r = Fixed { negative: false, mag: r.mag };
let reduced = if abs_r.ge_mag(half_pi) {
pi.sub(abs_r)
} else {
abs_r
};
let s = sin_taylor(reduced, w);
if sign {
s.neg()
} else {
s
}
}
fn atan_taylor(x: Fixed, w: u32) -> Fixed {
let x2 = x.mul(x, w);
let mut sum = x;
let mut term = x; let mut k: u128 = 1;
loop {
term = term.mul(x2, w);
let contrib = term.div_small(2 * k + 1);
if contrib.is_zero() {
break;
}
if k % 2 == 1 {
sum = sum.sub(contrib);
} else {
sum = sum.add(contrib);
}
k += 1;
if k > 300 {
break;
}
}
sum
}
pub(crate) fn atan_fixed(v_w: Fixed, w: u32) -> Fixed {
#[cfg(feature = "perf-trace")]
let _atan_span = ::tracing::info_span!("atan_fixed").entered();
#[cfg(feature = "perf-trace")]
let _setup_span = ::tracing::info_span!("setup").entered();
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let sign = v_w.negative;
let mut x = Fixed { negative: false, mag: v_w.mag };
let mut add_half_pi = false;
if x.ge_mag(one_w) && x != one_w {
x = one_w.div(x, w); add_half_pi = true;
}
#[cfg(feature = "perf-trace")]
drop(_setup_span);
#[cfg(feature = "perf-trace")]
let _halvings_span = ::tracing::info_span!("halvings").entered();
let halving_threshold = one_w.div_small(5); let mut halvings: u32 = 0;
while x.ge_mag(halving_threshold) && halvings < 8 {
let x2 = x.mul(x, w);
let denom = one_w.add(one_w.add(x2).sqrt(w));
x = x.div(denom, w);
halvings += 1;
}
#[cfg(feature = "perf-trace")]
drop(_halvings_span);
#[cfg(feature = "perf-trace")]
let _taylor_span = ::tracing::info_span!("taylor").entered();
let mut result = atan_taylor(x, w);
#[cfg(feature = "perf-trace")]
drop(_taylor_span);
#[cfg(feature = "perf-trace")]
let _reasm_span = ::tracing::info_span!("reassemble").entered();
result = result.shl(halvings);
if add_half_pi {
result = wide_half_pi(w).sub(result);
}
if sign {
result.neg()
} else {
result
}
}
#[inline]
#[must_use]
pub(crate) fn sin_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + STRICT_GUARD;
sin_fixed(to_fixed(raw), w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("sin", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn sin_with<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + working_digits;
sin_fixed(to_fixed_w(raw, working_digits), w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("sin", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn cos_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 10_i128.pow(SCALE);
}
let w = SCALE + STRICT_GUARD;
let arg = to_fixed(raw).add(wide_half_pi(w));
sin_fixed(arg, w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("cos", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn cos_with<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 10_i128.pow(SCALE);
}
let w = SCALE + working_digits;
let arg = to_fixed_w(raw, working_digits).add(wide_half_pi(w));
sin_fixed(arg, w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("cos", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn tan_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + STRICT_GUARD;
let v = to_fixed(raw);
let sin_w = sin_fixed(v, w);
let cos_w = sin_fixed(v.add(wide_half_pi(w)), w);
assert!(
!cos_w.is_zero(),
"tan: cosine is zero (argument is an odd multiple of pi/2)"
);
sin_w
.div(cos_w, w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("tan", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn tan_with<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + working_digits;
let v = to_fixed_w(raw, working_digits);
let sin_w = sin_fixed(v, w);
let cos_w = sin_fixed(v.add(wide_half_pi(w)), w);
assert!(
!cos_w.is_zero(),
"tan: cosine is zero (argument is an odd multiple of pi/2)"
);
sin_w
.div(cos_w, w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("tan", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn atan_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return <D38<SCALE> as DecimalConstants>::quarter_pi().0;
}
if raw == -one_bits {
return -<D38<SCALE> as DecimalConstants>::quarter_pi().0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + STRICT_GUARD;
atan_fixed(to_fixed(raw), w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("atan", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn atan_with<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return <D38<SCALE> as DecimalConstants>::quarter_pi().0;
}
if raw == -one_bits {
return -<D38<SCALE> as DecimalConstants>::quarter_pi().0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + working_digits;
atan_fixed(to_fixed_w(raw, working_digits), w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("atan", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn asin_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(raw);
let abs_v = Fixed { negative: false, mag: v.mag };
assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "asin: argument out of domain [-1, 1]");
if abs_v == one_w {
let hp = wide_half_pi(w);
let hp = if v.negative { hp.neg() } else { hp };
return hp
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("asin", SCALE));
}
let half_w = one_w.halve();
let asin_w = if !abs_v.ge_mag(half_w) {
let denom = one_w.sub(v.mul(v, w)).sqrt(w);
atan_fixed(v.div(denom, w), w)
} else {
let inner = one_w.sub(abs_v).halve();
let inner_sqrt = inner.sqrt(w);
let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
if v.negative { result_abs.neg() } else { result_abs }
};
asin_w
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("asin", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn asin_with<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() {
return raw;
}
let w = SCALE + working_digits;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed_w(raw, working_digits);
let abs_v = Fixed { negative: false, mag: v.mag };
assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "asin: argument out of domain [-1, 1]");
if abs_v == one_w {
let hp = wide_half_pi(w);
let hp = if v.negative { hp.neg() } else { hp };
return hp
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("asin", SCALE));
}
let half_w = one_w.halve();
let asin_w = if !abs_v.ge_mag(half_w) {
let denom = one_w.sub(v.mul(v, w)).sqrt(w);
atan_fixed(v.div(denom, w), w)
} else {
let inner = one_w.sub(abs_v).halve();
let inner_sqrt = inner.sqrt(w);
let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
if v.negative { result_abs.neg() } else { result_abs }
};
asin_w
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("asin", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn acos_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return <D38<SCALE> as DecimalConstants>::half_pi().0;
}
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return 0;
}
if raw == -one_bits {
return <D38<SCALE> as DecimalConstants>::pi().0;
}
let w = SCALE + STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(raw);
let abs_v = Fixed { negative: false, mag: v.mag };
assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "acos: argument out of domain [-1, 1]");
let half_w = one_w.halve();
let asin_w = if abs_v == one_w {
let hp = wide_half_pi(w);
if v.negative { hp.neg() } else { hp }
} else if !abs_v.ge_mag(half_w) {
let denom = one_w.sub(v.mul(v, w)).sqrt(w);
atan_fixed(v.div(denom, w), w)
} else {
let inner = one_w.sub(abs_v).halve();
let inner_sqrt = inner.sqrt(w);
let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
if v.negative { result_abs.neg() } else { result_abs }
};
wide_half_pi(w)
.sub(asin_w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("acos", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn acos_with<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return <D38<SCALE> as DecimalConstants>::half_pi().0;
}
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return 0;
}
if raw == -one_bits {
return <D38<SCALE> as DecimalConstants>::pi().0;
}
let w = SCALE + working_digits;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed_w(raw, working_digits);
let abs_v = Fixed { negative: false, mag: v.mag };
assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "acos: argument out of domain [-1, 1]");
let half_w = one_w.halve();
let asin_w = if abs_v == one_w {
let hp = wide_half_pi(w);
if v.negative { hp.neg() } else { hp }
} else if !abs_v.ge_mag(half_w) {
let denom = one_w.sub(v.mul(v, w)).sqrt(w);
atan_fixed(v.div(denom, w), w)
} else {
let inner = one_w.sub(abs_v).halve();
let inner_sqrt = inner.sqrt(w);
let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
if v.negative { result_abs.neg() } else { result_abs }
};
wide_half_pi(w)
.sub(asin_w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("acos", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn atan2_strict<const SCALE: u32>(
y_raw: i128,
x_raw: i128,
mode: RoundingMode,
) -> i128 {
let w = SCALE + STRICT_GUARD;
atan2_kernel(to_fixed(y_raw), to_fixed(x_raw), y_raw, w)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("atan2", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn atan2_with<const SCALE: u32>(
y_raw: i128,
x_raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
let w = SCALE + working_digits;
atan2_kernel(
to_fixed_w(y_raw, working_digits),
to_fixed_w(x_raw, working_digits),
y_raw,
w,
)
.round_to_i128_with(w, SCALE, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("atan2", SCALE))
}
#[inline]
#[must_use]
pub(crate) fn sinh_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
sinh_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn sinh_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) {
return raw;
}
let w = scale + working_digits;
let v = to_fixed_w(raw, working_digits);
let ex = exp_fixed(v, w);
let enx = exp_fixed(v.neg(), w);
ex.sub(enx)
.halve()
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::sinh", scale))
}
#[inline]
#[must_use]
pub(crate) fn cosh_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
cosh_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn cosh_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 10_i128.pow(scale);
}
let w = scale + working_digits;
let v = to_fixed_w(raw, working_digits);
let ex = exp_fixed(v, w);
let enx = exp_fixed(v.neg(), w);
ex.add(enx)
.halve()
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::cosh", scale))
}
#[inline]
#[must_use]
pub(crate) fn tanh_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
tanh_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn tanh_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) {
return raw;
}
let w = scale + working_digits;
let v = to_fixed_w(raw, working_digits);
let ex = exp_fixed(v, w);
let enx = exp_fixed(v.neg(), w);
ex.sub(enx)
.div(ex.add(enx), w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::tanh", scale))
}
#[inline]
#[must_use]
pub(crate) fn asinh_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
asinh_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn asinh_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) {
return raw;
}
let w = scale + working_digits;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed_w(raw, working_digits);
let ax = Fixed { negative: false, mag: v.mag };
let inner = if ax.ge_mag(one_w) {
let inv = one_w.div(ax, w);
let root = one_w.add(inv.mul(inv, w)).sqrt(w);
ln_fixed(ax, w).add(ln_fixed(one_w.add(root), w))
} else {
let root = ax.mul(ax, w).add(one_w).sqrt(w);
ln_fixed(ax.add(root), w)
};
let signed = if raw < 0 { inner.neg() } else { inner };
signed
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::asinh", scale))
}
#[inline]
#[must_use]
pub(crate) fn acosh_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
acosh_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn acosh_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
let one_bits: i128 = 10_i128.pow(scale);
if raw == one_bits {
return 0;
}
let w = scale + working_digits;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed_w(raw, working_digits);
assert!(!v.negative && v.ge_mag(one_w), "D38::acosh: argument must be >= 1");
let two_w = one_w.double();
let inner = if v.ge_mag(two_w) {
let inv = one_w.div(v, w);
let root = one_w.sub(inv.mul(inv, w)).sqrt(w);
ln_fixed(v, w).add(ln_fixed(one_w.add(root), w))
} else {
let root = v.mul(v, w).sub(one_w).sqrt(w);
ln_fixed(v.add(root), w)
};
inner
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::acosh", scale))
}
#[inline]
#[must_use]
pub(crate) fn atanh_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
atanh_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn atanh_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) {
return raw;
}
let w = scale + working_digits;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed_w(raw, working_digits);
let ax = Fixed { negative: false, mag: v.mag };
assert!(!ax.ge_mag(one_w), "D38::atanh: argument out of domain (-1, 1)");
let ratio = one_w.add(v).div(one_w.sub(v), w);
ln_fixed(ratio, w)
.halve()
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::atanh", scale))
}
#[inline]
#[must_use]
pub(crate) fn to_degrees_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
to_degrees_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn to_degrees_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
let w = scale + working_digits;
to_fixed_w(raw, working_digits)
.mul_u128(180)
.div(wide_pi(w), w)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::to_degrees", scale))
}
#[inline]
#[must_use]
pub(crate) fn to_radians_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
to_radians_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn to_radians_with(
raw: i128,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
let w = scale + working_digits;
to_fixed_w(raw, working_digits)
.mul(wide_pi(w), w)
.div_small(180)
.round_to_i128_with(w, scale, mode)
.unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("D38::to_radians", scale))
}
#[inline]
fn small_x_linear_threshold_scale(scale: u32) -> i128 {
let thresh_exp = scale.saturating_sub((scale + 2) / 3);
10_i128.pow(thresh_exp)
}