use crate::algos::exp::exp_generic as eg;
use crate::algos::exp::exp_series_2limb::exp_fixed;
use crate::algos::support::fixed::Fixed;
use crate::algos::support::narrow_ziv::{self, WZiv};
use crate::algos::ln::ln_series_2limb::{STRICT_GUARD, ln_fixed};
use crate::algos::trig::trig_generic as tg;
use crate::int::types::Int;
use crate::support::rounding::{RoundingMode, is_nearest_mode};
use crate::types::consts::DecimalConstants;
macro_rules! int2_trig {
(strict $pub:ident, $core:ident) => {
pub(crate) fn $pub<const SCALE: u32>(raw: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128($core::<SCALE>(raw.as_i128(), mode))
}
};
(with $pub:ident, $core:ident) => {
pub(crate) fn $pub<const SCALE: u32>(
raw: Int<2>,
working_digits: u32,
mode: RoundingMode,
) -> Int<2> {
Int::<2>::from_i128($core::<SCALE>(raw.as_i128(), working_digits, mode))
}
};
(with_scale $pub:ident, $core:ident) => {
pub(crate) fn $pub(
raw: Int<2>,
scale: u32,
working_digits: u32,
mode: RoundingMode,
) -> Int<2> {
Int::<2>::from_i128($core(raw.as_i128(), scale, working_digits, mode))
}
};
(atan2_strict $pub:ident, $core:ident) => {
pub(crate) fn $pub<const SCALE: u32>(
y_raw: Int<2>,
x_raw: Int<2>,
mode: RoundingMode,
) -> Int<2> {
Int::<2>::from_i128($core::<SCALE>(y_raw.as_i128(), x_raw.as_i128(), mode))
}
};
(atan2_with $pub:ident, $core:ident) => {
pub(crate) fn $pub<const SCALE: u32>(
y_raw: Int<2>,
x_raw: Int<2>,
working_digits: u32,
mode: RoundingMode,
) -> Int<2> {
Int::<2>::from_i128($core::<SCALE>(
y_raw.as_i128(),
x_raw.as_i128(),
working_digits,
mode,
))
}
};
}
int2_trig!(strict sin_strict, sin_strict_raw);
int2_trig!(strict cos_strict, cos_strict_raw);
int2_trig!(strict tan_strict, tan_strict_raw);
int2_trig!(strict atan_strict, atan_strict_raw);
int2_trig!(strict asin_strict, asin_strict_raw);
int2_trig!(strict acos_strict, acos_strict_raw);
int2_trig!(atan2_strict atan2_strict, atan2_strict_raw);
int2_trig!(with sin_with, sin_with_raw);
int2_trig!(with cos_with, cos_with_raw);
int2_trig!(with tan_with, tan_with_raw);
int2_trig!(with atan_with, atan_with_raw);
int2_trig!(with asin_with, asin_with_raw);
int2_trig!(with acos_with, acos_with_raw);
int2_trig!(atan2_with atan2_with, atan2_with_raw);
int2_trig!(with_scale sinh_with, sinh_with_raw);
int2_trig!(with_scale cosh_with, cosh_with_raw);
int2_trig!(with_scale tanh_with, tanh_with_raw);
int2_trig!(with_scale asinh_with, asinh_with_raw);
int2_trig!(with_scale acosh_with, acosh_with_raw);
int2_trig!(with_scale atanh_with, atanh_with_raw);
int2_trig!(with_scale to_degrees_with, to_degrees_with_raw);
int2_trig!(with_scale to_radians_with, to_radians_with_raw);
#[inline]
pub(crate) const fn small_x_linear_threshold<const SCALE: u32>() -> i128 {
if SCALE == 0 {
return 0;
}
let thresh_exp = SCALE.saturating_sub(SCALE.div_ceil(3));
10_i128.pow(thresh_exp)
}
pub(crate) fn wide_pi(w: u32) -> Fixed {
debug_assert!(w <= 75, "wide_pi: working scale {w} exceeds Fixed capacity");
let words = crate::consts::pi_const_n::<4>(w, crate::support::rounding::RoundingMode::HalfToEven)
.limbs_le();
Fixed {
negative: false,
mag: [
(words[0] as u128) | ((words[1] as u128) << 64),
(words[2] as u128) | ((words[3] as u128) << 64),
],
}
}
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
}
fn cos_taylor(r: Fixed, w: u32) -> Fixed {
let r2 = r.mul(r, w);
let one_w = Fixed {
negative: false,
mag: Fixed::pow10(w),
};
let mut sum = one_w;
let mut term = one_w;
let mut k: u128 = 1;
loop {
term = term.mul(r2, w).div_small((2 * k - 1) * (2 * k));
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 quarter_pi = half_pi.halve();
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 = if reduced.ge_mag(quarter_pi) {
cos_taylor(half_pi.sub(reduced), w)
} else {
sin_taylor(reduced, w)
};
if sign { s.neg() } else { s }
}
pub(crate) fn sin_cos_fixed(v_w: Fixed, w: u32) -> (Fixed, Fixed) {
let tau = wide_tau(w);
let pi = wide_pi(w);
let half_pi = wide_half_pi(w);
let quarter_pi = half_pi.halve();
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 sin_neg = r.negative;
let abs_r = Fixed {
negative: false,
mag: r.mag,
};
let cos_neg = abs_r.ge_mag(half_pi); let sin_reduced = if cos_neg { pi.sub(abs_r) } else { abs_r };
let s_abs = if sin_reduced.ge_mag(quarter_pi) {
cos_taylor(half_pi.sub(sin_reduced), w)
} else {
sin_taylor(sin_reduced, w)
};
let cos_reduced = half_pi.sub(sin_reduced);
let c_abs = if cos_reduced.ge_mag(quarter_pi) {
cos_taylor(half_pi.sub(cos_reduced), w)
} else {
sin_taylor(cos_reduced, w)
};
let sin_result = if sin_neg { s_abs.neg() } else { s_abs };
let cos_result = if cos_neg { c_abs.neg() } else { c_abs };
(sin_result, cos_result)
}
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 }
}
fn sin_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
tg::sin_fixed::<WZiv>(narrow_ziv::lift(raw, g), w, narrow_ziv::pi_w(w))
}
fn cos_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
tg::cos_fixed::<WZiv>(narrow_ziv::lift(raw, g), w, narrow_ziv::pi_w(w))
}
fn tan_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let (s, c) = tg::sin_cos_fixed::<WZiv>(narrow_ziv::lift(raw, g), w, narrow_ziv::pi_w(w));
assert!(
c != WZiv::from_i128(0),
"tan: cosine is zero (argument is an odd multiple of pi/2)"
);
eg::div::<WZiv>(s, c, w)
}
fn atan_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
tg::atan_fixed::<WZiv>(narrow_ziv::lift(raw, g), w, narrow_ziv::pi_w(w))
}
pub(crate) fn asin_work_ziv(v: WZiv, w: u32) -> WZiv {
let zero = WZiv::from_i128(0);
let one_w = eg::one::<WZiv>(w);
let pi = narrow_ziv::pi_w(w);
let hp = pi >> 1;
let neg = v < zero;
let av = if neg { -v } else { v };
if av == one_w {
return if neg { -hp } else { hp };
}
let half_w = one_w >> 1;
let r = if av < half_w {
let denom = eg::sqrt_fixed::<WZiv>(one_w - eg::mul::<WZiv>(av, av, w), w);
tg::atan_fixed::<WZiv>(eg::div::<WZiv>(av, denom, w), w, pi)
} else {
let inner = (one_w - av) >> 1;
let inner_sqrt = eg::sqrt_fixed::<WZiv>(inner, w);
let inner_denom =
eg::sqrt_fixed::<WZiv>(one_w - eg::mul::<WZiv>(inner_sqrt, inner_sqrt, w), w);
let inner_asin =
tg::atan_fixed::<WZiv>(eg::div::<WZiv>(inner_sqrt, inner_denom, w), w, pi);
hp - inner_asin - inner_asin
};
if neg { -r } else { r }
}
pub(crate) fn asin_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
asin_work_ziv(narrow_ziv::lift(raw, g), w)
}
pub(crate) fn acos_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
(narrow_ziv::pi_w(w) >> 1) - asin_work_ziv(narrow_ziv::lift(raw, g), w)
}
pub(crate) fn atan2_ziv(y_raw: i128, x_raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let zero = WZiv::from_i128(0);
let pi = narrow_ziv::pi_w(w);
let hp = pi >> 1;
if x_raw == 0 {
return if y_raw > 0 {
hp
} else if y_raw < 0 {
-hp
} else {
zero
};
}
let y = narrow_ziv::lift(y_raw, g);
let x = narrow_ziv::lift(x_raw, g);
let ay = if y < zero { -y } else { y };
let ax = if x < zero { -x } else { x };
let base = if ax >= ay {
tg::atan_fixed::<WZiv>(eg::div::<WZiv>(y, x, w), w, pi)
} else {
let inv = tg::atan_fixed::<WZiv>(eg::div::<WZiv>(x, y, w), w, pi);
let same_sign = (y < zero) == (x < zero);
if same_sign { hp - inv } else { (-hp) - inv }
};
if x_raw > 0 {
base
} else if y_raw >= 0 {
base + pi
} else {
base - pi
}
}
fn adjust_bounded_extremum_raw(result: i128, scale: u32, mode: RoundingMode) -> i128 {
if is_nearest_mode(mode) {
return result;
}
let one = 10_i128.pow(scale);
if result == one {
match mode {
RoundingMode::Floor | RoundingMode::Trunc => one - 1,
_ => result,
}
} else if result == -one {
match mode {
RoundingMode::Ceiling | RoundingMode::Trunc => -one + 1,
_ => result,
}
} else {
result
}
}
#[inline]
#[must_use]
pub(crate) fn sin_strict_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
return raw;
}
let w = SCALE + STRICT_GUARD;
let r = match sin_fixed(to_fixed(raw), w).round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("sin", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| sin_ziv(raw, SCALE, g)),
};
adjust_bounded_extremum_raw(r, SCALE, mode)
}
#[inline]
#[must_use]
pub(crate) fn sin_with_raw<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
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_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 10_i128.pow(SCALE);
}
let w = SCALE + STRICT_GUARD;
let v = sin_fixed(to_fixed(raw).add(wide_half_pi(w)), w);
let r = match v.round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("cos", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| cos_ziv(raw, SCALE, g)),
};
adjust_bounded_extremum_raw(r, SCALE, mode)
}
#[inline]
#[must_use]
pub(crate) fn cos_with_raw<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_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
return raw;
}
let w = SCALE + STRICT_GUARD;
let (sin_w, cos_w) = sin_cos_fixed(to_fixed(raw), w);
assert!(
!cos_w.is_zero(),
"tan: cosine is zero (argument is an odd multiple of pi/2)"
);
match sin_w.div(cos_w, w).round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("tan", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| tan_ziv(raw, SCALE, g)),
}
}
#[inline]
#[must_use]
pub(crate) fn tan_with_raw<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
return raw;
}
let w = SCALE + working_digits;
let (sin_w, cos_w) = sin_cos_fixed(to_fixed_w(raw, working_digits), 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_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
let one_bits: i128 = 10_i128.pow(SCALE);
if is_nearest_mode(mode) {
if raw == one_bits {
return <crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::quarter_pi().0.as_i128();
}
if raw == -one_bits {
return -<crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::quarter_pi().0.as_i128();
}
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
return raw;
}
let w = SCALE + STRICT_GUARD;
match atan_fixed(to_fixed(raw), w).round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("atan", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| atan_ziv(raw, SCALE, g)),
}
}
#[inline]
#[must_use]
pub(crate) fn atan_with_raw<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 is_nearest_mode(mode) {
if raw == one_bits {
return <crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::quarter_pi().0.as_i128();
}
if raw == -one_bits {
return -<crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::quarter_pi().0.as_i128();
}
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
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_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
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]"
);
let asin_w = if abs_v == one_w {
let hp = wide_half_pi(w);
if v.negative { hp.neg() } else { hp }
} else {
let half_w = one_w.halve();
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
}
}
};
match asin_w.round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("asin", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| asin_ziv(raw, SCALE, g)),
}
}
#[inline]
#[must_use]
pub(crate) fn asin_with_raw<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold::<SCALE>() && is_nearest_mode(mode) {
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_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
if raw == 0 && is_nearest_mode(mode) {
return <crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::half_pi().0.as_i128();
}
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return 0;
}
if raw == -one_bits && is_nearest_mode(mode) {
return <crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::pi().0.as_i128();
}
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
}
};
match wide_half_pi(w)
.sub(asin_w)
.round_to_i128_clear_of_tie(w, SCALE, mode)
{
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("acos", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| acos_ziv(raw, SCALE, g)),
}
}
#[inline]
#[must_use]
pub(crate) fn acos_with_raw<const SCALE: u32>(
raw: i128,
working_digits: u32,
mode: RoundingMode,
) -> i128 {
if raw == 0 && is_nearest_mode(mode) {
return <crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::half_pi().0.as_i128();
}
let one_bits: i128 = 10_i128.pow(SCALE);
if raw == one_bits {
return 0;
}
if raw == -one_bits && is_nearest_mode(mode) {
return <crate::D<crate::int::types::Int<2>, SCALE> as DecimalConstants>::pi().0.as_i128();
}
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_raw<const SCALE: u32>(y_raw: i128, x_raw: i128, mode: RoundingMode) -> i128 {
let w = SCALE + STRICT_GUARD;
match atan2_kernel(to_fixed(y_raw), to_fixed(x_raw), y_raw, w)
.round_to_i128_clear_of_tie(w, SCALE, mode)
{
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("atan2", SCALE)
}),
None => narrow_ziv::walk(STRICT_GUARD, SCALE, mode, |g| {
atan2_ziv(y_raw, x_raw, SCALE, g)
}),
}
}
#[inline]
#[must_use]
pub(crate) fn atan2_with_raw<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: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128(sinh_strict_raw(raw.as_i128(), SCALE, mode))
}
fn sinh_eval_fixed(raw: i128, working_digits: u32, w: u32) -> Fixed {
let v = to_fixed_w(raw, working_digits);
let av = Fixed {
negative: false,
mag: v.mag,
};
let ex = exp_fixed(av, w);
let one_w = Fixed {
negative: false,
mag: Fixed::pow10(w),
};
let enx = one_w.div(ex, w);
let sh = ex.sub(enx).halve();
if raw < 0 { sh.neg() } else { sh }
}
fn sinh_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let v = narrow_ziv::lift(raw, g);
let av = if v < WZiv::from_i128(0) { -v } else { v };
let sh = eg::sinh_pos::<WZiv>(av, w);
if raw < 0 { -sh } else { sh }
}
fn sinh_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) {
return crate::support::rounding::tiny_odd_expanding_directed(raw, 0, 1, mode);
}
let w = scale + STRICT_GUARD;
if crate::algos::exp::exp_series_2limb::hyper_needs_wide_narrow(raw, scale, w) {
return narrow_ziv::walk(STRICT_GUARD, scale, mode, |g| sinh_ziv(raw, scale, g));
}
match sinh_eval_fixed(raw, STRICT_GUARD, w).round_to_i128_clear_of_tie(w, scale, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::sinh", scale)
}),
None => narrow_ziv::walk(STRICT_GUARD, scale, mode, |g| sinh_ziv(raw, scale, g)),
}
}
#[inline]
#[must_use]
pub(crate) fn sinh_with_raw(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 crate::support::rounding::tiny_odd_expanding_directed(raw, 0, 1, mode);
}
let w = scale + working_digits;
if crate::algos::exp::exp_series_2limb::hyper_needs_wide_narrow(raw, scale, w) {
return crate::algos::exp::exp_series_2limb::sinh_wide_narrow_raw(
raw,
scale,
working_digits,
mode,
);
}
sinh_eval_fixed(raw, working_digits, w)
.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: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128(cosh_strict_raw(raw.as_i128(), SCALE, mode))
}
fn cosh_eval_fixed(raw: i128, working_digits: u32, w: u32) -> Fixed {
let v = to_fixed_w(raw, working_digits);
let av = Fixed {
negative: false,
mag: v.mag,
};
let ex = exp_fixed(av, w);
let one_w = Fixed {
negative: false,
mag: Fixed::pow10(w),
};
let enx = one_w.div(ex, w);
ex.add(enx).halve()
}
fn cosh_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let v = narrow_ziv::lift(raw, g);
let av = if v < WZiv::from_i128(0) { -v } else { v };
eg::cosh_pos::<WZiv>(av, w)
}
fn cosh_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 10_i128.pow(scale);
}
let w = scale + STRICT_GUARD;
if crate::algos::exp::exp_series_2limb::hyper_needs_wide_narrow(raw, scale, w) {
return narrow_ziv::walk_never_exact(STRICT_GUARD, scale, mode, |g| {
cosh_ziv(raw, scale, g)
});
}
match cosh_eval_fixed(raw, STRICT_GUARD, w).round_to_i128_clear_of_tie(w, scale, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::cosh", scale)
}),
None => narrow_ziv::walk_never_exact(STRICT_GUARD, scale, mode, |g| {
cosh_ziv(raw, scale, g)
}),
}
}
#[inline]
#[must_use]
pub(crate) fn cosh_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 10_i128.pow(scale);
}
let w = scale + working_digits;
if crate::algos::exp::exp_series_2limb::hyper_needs_wide_narrow(raw, scale, w)
|| !crate::support::rounding::is_nearest_mode(mode)
{
return crate::algos::exp::exp_series_2limb::cosh_wide_narrow_raw(
raw,
scale,
working_digits,
mode,
);
}
cosh_eval_fixed(raw, working_digits, w)
.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: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128(tanh_strict_raw(raw.as_i128(), SCALE, mode))
}
fn tanh_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let v = narrow_ziv::lift(raw, g);
let av = if v < WZiv::from_i128(0) { -v } else { v };
let th = eg::tanh_pos::<WZiv>(av, w);
if raw < 0 { -th } else { th }
}
fn tanh_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) {
return crate::support::rounding::tiny_odd_compressing_directed(raw, 0, 1, mode);
}
let w = scale + STRICT_GUARD;
match tanh_eval_fixed(raw, STRICT_GUARD, w) {
(th, true) => th.round_to_i128_with(w, scale, mode).unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::tanh", scale)
}),
(th, false) => match th.round_to_i128_clear_of_tie(w, scale, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::tanh", scale)
}),
None => narrow_ziv::walk(STRICT_GUARD, scale, mode, |g| tanh_ziv(raw, scale, g)),
},
}
}
fn tanh_eval_fixed(raw: i128, working_digits: u32, w: u32) -> (Fixed, bool) {
let one_w = Fixed {
negative: false,
mag: Fixed::pow10(w),
};
let neg = raw < 0;
let scale = w - working_digits;
let thr_x = (w as i128) * 1152 / 1000 + 2;
let saturated = one_w.sub(Fixed::from_u128_mag(1, false));
let (th, sat) = if raw.abs() / 10_i128.pow(scale) > thr_x {
(saturated, true)
} else {
let v = to_fixed_w(raw, working_digits);
let av = Fixed {
negative: false,
mag: v.mag,
};
let m = exp_fixed(av.double().neg(), w);
if m.is_zero() {
(saturated, true)
} else {
(one_w.sub(m).div(one_w.add(m), w), false)
}
};
(if neg { th.neg() } else { th }, sat)
}
#[inline]
#[must_use]
pub(crate) fn tanh_with_raw(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 crate::support::rounding::tiny_odd_compressing_directed(raw, 0, 1, mode);
}
let w = scale + working_digits;
let (th, _saturated) = tanh_eval_fixed(raw, working_digits, w);
th.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: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128(asinh_strict_raw(raw.as_i128(), SCALE, mode))
}
fn asinh_eval_fixed(raw: i128, working_digits: u32, w: u32) -> Fixed {
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)
};
if raw < 0 { inner.neg() } else { inner }
}
fn asinh_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let zero = WZiv::from_i128(0);
let one_w = eg::one::<WZiv>(w);
let ln2 = narrow_ziv::ln2_w(w);
let v = narrow_ziv::lift(raw, g);
let av = if v < zero { -v } else { v };
let inner = if av >= one_w {
let inv = eg::div::<WZiv>(one_w, av, w);
let root = eg::sqrt_fixed::<WZiv>(one_w + eg::mul::<WZiv>(inv, inv, w), w);
eg::ln_fixed::<WZiv>(av, w, ln2) + eg::ln_fixed::<WZiv>(one_w + root, w, ln2)
} else {
let root = eg::sqrt_fixed::<WZiv>(eg::mul::<WZiv>(av, av, w) + one_w, w);
eg::ln_fixed::<WZiv>(av + root, w, ln2)
};
if raw < 0 { -inner } else { inner }
}
fn asinh_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) && is_nearest_mode(mode) {
return raw;
}
let w = scale + STRICT_GUARD;
match asinh_eval_fixed(raw, STRICT_GUARD, w).round_to_i128_clear_of_tie(w, scale, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::asinh", scale)
}),
None => narrow_ziv::walk(STRICT_GUARD, scale, mode, |g| asinh_ziv(raw, scale, g)),
}
}
#[inline]
#[must_use]
pub(crate) fn asinh_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) && is_nearest_mode(mode) {
return raw;
}
let w = scale + working_digits;
asinh_eval_fixed(raw, working_digits, w)
.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: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128(acosh_strict_raw(raw.as_i128(), SCALE, mode))
}
fn acosh_eval_fixed(raw: i128, working_digits: u32, w: u32) -> Fixed {
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();
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)
}
}
fn acosh_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let one_w = eg::one::<WZiv>(w);
let ln2 = narrow_ziv::ln2_w(w);
let v = narrow_ziv::lift(raw, g);
let two_w = one_w + one_w;
if v >= two_w {
let inv = eg::div::<WZiv>(one_w, v, w);
let root = eg::sqrt_fixed::<WZiv>(one_w - eg::mul::<WZiv>(inv, inv, w), w);
eg::ln_fixed::<WZiv>(v, w, ln2) + eg::ln_fixed::<WZiv>(one_w + root, w, ln2)
} else {
let root = eg::sqrt_fixed::<WZiv>(eg::mul::<WZiv>(v, v, w) - one_w, w);
eg::ln_fixed::<WZiv>(v + root, w, ln2)
}
}
fn acosh_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> i128 {
let one_bits: i128 = 10_i128.pow(scale);
if raw == one_bits {
return 0;
}
let w = scale + STRICT_GUARD;
match acosh_eval_fixed(raw, STRICT_GUARD, w).round_to_i128_clear_of_tie(w, scale, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::acosh", scale)
}),
None => narrow_ziv::walk_near_special(STRICT_GUARD, scale, mode, |g| {
acosh_ziv(raw, scale, g)
}),
}
}
#[inline]
#[must_use]
pub(crate) fn acosh_with_raw(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;
acosh_eval_fixed(raw, working_digits, w)
.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: Int<2>, mode: RoundingMode) -> Int<2> {
Int::<2>::from_i128(atanh_strict_raw(raw.as_i128(), SCALE, mode))
}
fn atanh_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let zero = WZiv::from_i128(0);
let one_w = eg::one::<WZiv>(w);
let ln2 = narrow_ziv::ln2_w(w);
let v = narrow_ziv::lift(raw, g);
let av = if v < zero { -v } else { v };
let inner =
(eg::ln_fixed::<WZiv>(one_w + av, w, ln2) - eg::ln_fixed::<WZiv>(one_w - av, w, ln2)) >> 1;
if raw < 0 { -inner } else { inner }
}
fn atanh_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) && is_nearest_mode(mode) {
return raw;
}
let w = scale + STRICT_GUARD;
match atanh_eval_fixed(raw, STRICT_GUARD, w).round_to_i128_clear_of_tie(w, scale, mode) {
Some(v) => v.unwrap_or_else(|| {
crate::support::diagnostics::overflow_panic_with_scale("D38::atanh", scale)
}),
None => narrow_ziv::walk_near_special(STRICT_GUARD, scale, mode, |g| {
atanh_ziv(raw, scale, g)
}),
}
}
fn atanh_eval_fixed(raw: i128, working_digits: u32, w: u32) -> Fixed {
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)"
);
if one_w.sub(ax).mul_u128(50).ge_mag(one_w) {
let r = one_w.add(v).div(one_w.sub(v), w);
ln_fixed(r, w).halve()
} else {
let ln_num = ln_fixed(one_w.add(v), w);
let ln_den = ln_fixed(one_w.sub(v), w);
ln_num.sub(ln_den).halve()
}
}
#[inline]
#[must_use]
pub(crate) fn atanh_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> i128 {
if raw == 0 {
return 0;
}
if raw.abs() <= small_x_linear_threshold_scale(scale) && is_nearest_mode(mode) {
return raw;
}
let w = scale + working_digits;
atanh_eval_fixed(raw, working_digits, w)
.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: Int<2>, mode: RoundingMode) -> Int<2> {
to_degrees_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn to_degrees_with_raw(
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: Int<2>, mode: RoundingMode) -> Int<2> {
to_radians_with(raw, SCALE, STRICT_GUARD, mode)
}
#[inline]
#[must_use]
pub(crate) fn to_radians_with_raw(
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.div_ceil(3));
10_i128.pow(thresh_exp)
}
#[cfg(test)]
mod near_tie_pins {
use super::*;
const S38: u32 = 38;
const ONE38: i128 = 10_i128.pow(38);
#[test]
fn sin_directed_tiny_x_d38_s38() {
assert_eq!(sin_strict_raw::<S38>(1, RoundingMode::Floor), 0, "sin Floor");
assert_eq!(sin_strict_raw::<S38>(1, RoundingMode::Trunc), 0, "sin Trunc");
assert_eq!(sin_strict_raw::<S38>(1, RoundingMode::Ceiling), 1, "sin Ceiling");
assert_eq!(sin_strict_raw::<S38>(-1, RoundingMode::Ceiling), 0, "sin(−x) Ceiling");
assert_eq!(sin_strict_raw::<S38>(-1, RoundingMode::Floor), -1, "sin(−x) Floor");
}
#[test]
fn cos_directed_tiny_x_d38_s38() {
assert_eq!(cos_strict_raw::<S38>(1, RoundingMode::Floor), ONE38 - 1, "cos Floor");
assert_eq!(cos_strict_raw::<S38>(1, RoundingMode::Trunc), ONE38 - 1, "cos Trunc");
assert_eq!(cos_strict_raw::<S38>(1, RoundingMode::Ceiling), ONE38, "cos Ceiling");
}
#[test]
fn tan_directed_tiny_x_d38_s38() {
assert_eq!(tan_strict_raw::<S38>(1, RoundingMode::Ceiling), 2, "tan Ceiling");
assert_eq!(tan_strict_raw::<S38>(1, RoundingMode::Floor), 1, "tan Floor");
assert_eq!(tan_strict_raw::<S38>(-1, RoundingMode::Floor), -2, "tan(−x) Floor");
}
#[test]
fn atan_directed_tiny_x_d38_s38() {
assert_eq!(atan_strict_raw::<S38>(1, RoundingMode::Floor), 0, "atan Floor");
assert_eq!(atan_strict_raw::<S38>(1, RoundingMode::Trunc), 0, "atan Trunc");
assert_eq!(atan_strict_raw::<S38>(1, RoundingMode::Ceiling), 1, "atan Ceiling");
}
#[test]
fn asin_directed_tiny_x_d38_s38() {
assert_eq!(asin_strict_raw::<S38>(1, RoundingMode::Ceiling), 2, "asin Ceiling");
assert_eq!(asin_strict_raw::<S38>(1, RoundingMode::Floor), 1, "asin Floor");
}
#[test]
fn asinh_directed_tiny_x_d38_s38() {
assert_eq!(asinh_strict_raw(1, S38, RoundingMode::Floor), 0, "asinh Floor");
assert_eq!(asinh_strict_raw(1, S38, RoundingMode::Ceiling), 1, "asinh Ceiling");
}
#[test]
fn atanh_directed_tiny_x_d38_s38() {
assert_eq!(atanh_strict_raw(1, S38, RoundingMode::Ceiling), 2, "atanh Ceiling");
assert_eq!(atanh_strict_raw(1, S38, RoundingMode::Floor), 1, "atanh Floor");
}
#[test]
fn atan2_directed_tiny_ratio_d38_s38() {
assert_eq!(
atan2_strict_raw::<S38>(1, ONE38, RoundingMode::Floor),
0,
"atan2 Floor"
);
assert_eq!(
atan2_strict_raw::<S38>(1, ONE38, RoundingMode::Ceiling),
1,
"atan2 Ceiling"
);
}
#[test]
fn tan_directed_tiny_x_d18_s18() {
assert_eq!(tan_strict_raw::<18>(1, RoundingMode::Ceiling), 2, "tan D18 Ceiling");
assert_eq!(sin_strict_raw::<18>(1, RoundingMode::Floor), 0, "sin D18 Floor");
}
#[test]
fn cosh_nearest_exact_half_d38_s38() {
let raw = 10_i128.pow(19);
for (mode, what) in [
(RoundingMode::HalfToEven, "HalfToEven"),
(RoundingMode::HalfAwayFromZero, "HalfAwayFromZero"),
(RoundingMode::HalfTowardZero, "HalfTowardZero"),
] {
assert_eq!(
cosh_strict_raw(raw, S38, mode),
ONE38 + 1,
"cosh(1e-19) {what}"
);
}
assert_eq!(
cosh_strict_raw(3 * raw, S38, RoundingMode::HalfToEven),
ONE38 + 5,
"cosh(3e-19) HalfToEven"
);
}
#[test]
fn cos_nearest_exact_half_d38_s38() {
let raw = 10_i128.pow(19);
for (mode, what) in [
(RoundingMode::HalfToEven, "HalfToEven"),
(RoundingMode::HalfAwayFromZero, "HalfAwayFromZero"),
(RoundingMode::HalfTowardZero, "HalfTowardZero"),
] {
assert_eq!(
cos_strict_raw::<S38>(raw, mode),
ONE38,
"cos(1e-19) {what}"
);
}
}
#[test]
fn cos_nearest_exact_half_d38_s36() {
let one36 = 10_i128.pow(36);
assert_eq!(
cos_strict_raw::<36>(10_i128.pow(18), RoundingMode::HalfTowardZero),
one36,
"cos(1e-18) s36 HalfTowardZero"
);
assert_eq!(
cosh_strict_raw(10_i128.pow(18), 36, RoundingMode::HalfToEven),
one36 + 1,
"cosh(1e-18) s36 HalfToEven"
);
}
#[test]
fn public_path_d38_and_d18_directed_tiny_x() {
let x38 = crate::D::<Int<2>, 38>(Int::<2>::from_i128(1));
assert_eq!(x38.sin_strict_with(RoundingMode::Floor).0.as_i128(), 0, "public sin Floor");
assert_eq!(x38.tan_strict_with(RoundingMode::Ceiling).0.as_i128(), 2, "public tan Ceiling");
assert_eq!(x38.asin_strict_with(RoundingMode::Ceiling).0.as_i128(), 2, "public asin Ceiling");
assert_eq!(
x38.cosh_strict_with(RoundingMode::HalfToEven).0.as_i128(),
10_i128.pow(38),
"public cosh(1e-38) HalfToEven (1 + x²/2 = 1 + 5e-77 → 1)"
);
let x18 = crate::D::<Int<1>, 18>(Int::<1>::from_i128(1));
assert_eq!(x18.tan_strict_with(RoundingMode::Ceiling).0.as_i128(), 2, "public D18 tan Ceiling");
}
}
#[cfg(test)]
mod hyper_fast_path_validity {
use super::*;
const MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Ceiling,
RoundingMode::Floor,
RoundingMode::Trunc,
];
fn fast_hyper_raw(raw: i128, scale: u32, mode: RoundingMode, is_cosh: bool) -> Option<i128> {
let w = scale + STRICT_GUARD;
std::panic::catch_unwind(|| {
let v = to_fixed_w(raw, STRICT_GUARD);
let av = Fixed {
negative: false,
mag: v.mag,
};
let ex = exp_fixed(av, w);
let one_w = Fixed {
negative: false,
mag: Fixed::pow10(w),
};
let enx = one_w.div(ex, w);
let res = if is_cosh {
ex.add(enx).halve()
} else {
let sh = ex.sub(enx).halve();
if raw < 0 { sh.neg() } else { sh }
};
res.round_to_i128_with(w, scale, mode)
})
.unwrap_or(None)
}
fn run(is_cosh: bool) {
std::panic::set_hook(Box::new(|_| {}));
let mut checked = 0u64;
for scale in 0u32..=38 {
let one_s = 10f64.powi(scale as i32);
let mut x10 = 1u64;
while x10 <= 1000 {
let x = x10 as f64 / 10.0;
for sign in [1i128, -1] {
let raw_f = sign as f64 * x * one_s;
if raw_f.abs() >= (i128::MAX as f64) {
x10 += 1;
continue;
}
let raw = raw_f as i128;
if raw == 0 || raw.abs() <= small_x_linear_threshold_scale(scale) {
continue; }
let w = scale + STRICT_GUARD;
if crate::algos::exp::exp_series_2limb::hyper_needs_wide_narrow(raw, scale, w) {
continue;
}
for mode in MODES {
let wide = match std::panic::catch_unwind(|| {
if is_cosh {
crate::algos::exp::exp_series_2limb::cosh_wide_narrow_raw(
raw,
scale,
STRICT_GUARD,
mode,
)
} else {
crate::algos::exp::exp_series_2limb::sinh_wide_narrow_raw(
raw,
scale,
STRICT_GUARD,
mode,
)
}
}) {
Ok(v) => v,
Err(_) => continue,
};
let fast = fast_hyper_raw(raw, scale, mode, is_cosh);
assert_eq!(
fast,
Some(wide),
"{} fast != wide at scale={scale} raw={raw} mode={mode:?}",
if is_cosh { "cosh" } else { "sinh" }
);
checked += 1;
}
}
x10 += 1;
}
}
assert!(checked > 50_000, "too few cells checked: {checked}");
}
#[test]
fn sinh_fast_bit_identical_to_wide_d38() {
run(false);
}
#[test]
fn cosh_fast_bit_identical_to_wide_d38() {
run(true);
}
}