use crate::int::types::traits::BigInt;
use crate::support::rounding::RoundingMode;
pub(crate) trait WideTrigCore {
type W: BigInt + Copy + PartialEq;
type Wexp: BigInt + Copy + PartialEq;
type Wagm: BigInt + Copy + PartialEq;
type Storage: BigInt + Copy + PartialEq;
const GUARD: u32;
fn storage_zero() -> Self::Storage;
fn storage_one(scale: u32) -> Self::Storage;
fn storage_max() -> Self::Storage;
fn storage_min() -> Self::Storage;
fn zero() -> Self::W;
fn to_work_scaled(raw: Self::Storage, working_digits: u32) -> Self::W;
fn to_work(raw: Self::Storage) -> Self::W;
fn round_to_storage_with(
v: Self::W,
w: u32,
target: u32,
mode: RoundingMode,
) -> Self::Storage;
fn to_work_scaled_agm(raw: Self::Storage, working_digits: u32) -> Self::Wagm;
fn round_to_storage_with_agm(
v: Self::Wagm,
w: u32,
target: u32,
mode: RoundingMode,
) -> Self::Storage;
fn round_to_storage_directed(
base_guard: u32,
target: u32,
mode: RoundingMode,
recompute: &mut dyn FnMut(u32) -> Self::W,
) -> Self::Storage;
fn round_to_storage_directed_never_exact(
base_guard: u32,
target: u32,
mode: RoundingMode,
recompute: &mut dyn FnMut(u32) -> Self::W,
) -> Self::Storage;
fn exp_fixed<const SCALE: u32>(v_w: Self::W, w: u32) -> Self::W;
fn ln_fixed<const SCALE: u32>(v_w: Self::W, w: u32) -> Self::W;
fn sin_fixed<const SCALE: u32>(v_w: Self::W, w: u32) -> Self::W;
fn cos_fixed<const SCALE: u32>(v_w: Self::W, w: u32) -> Self::W;
fn sin_cos_fixed<const SCALE: u32>(v_w: Self::W, w: u32) -> (Self::W, Self::W);
fn atan_fixed<const SCALE: u32>(v_w: Self::W, w: u32) -> Self::W;
fn div(a: Self::W, b: Self::W, w: u32) -> Self::W;
fn mul(a: Self::W, b: Self::W, w: u32) -> Self::W;
fn sqrt_fixed(v: Self::W, w: u32) -> Self::W;
fn log1p_fixed(t: Self::W, w: u32) -> Self::W;
fn bit_length(v: Self::W) -> u32;
fn exp_result_int_digits(mag_at_scale: Self::W, scale: u32) -> u32;
fn sinh_pos_wide<const SCALE: u32>(av_w: Self::W, w: u32) -> Self::W;
fn cosh_pos_wide<const SCALE: u32>(av_w: Self::W, w: u32) -> Self::W;
fn tanh_pos_wide<const SCALE: u32>(av_w: Self::W, w: u32) -> Self::W;
fn ln_fixed_routed_agm<const SCALE: u32>(v_w: Self::Wagm, w: u32) -> Self::Wagm;
fn round_to_storage_directed_near_special(
base_guard: u32,
target: u32,
mode: RoundingMode,
recompute: &mut dyn FnMut(u32) -> Self::W,
) -> Self::Storage;
fn one(w: u32) -> Self::W;
fn lit(n: u128) -> Self::W;
fn ln2<const SCALE: u32>(w: u32) -> Self::W;
fn div_cached(a: Self::W, b: Self::W, pow10_w: Self::W) -> Self::W;
fn round_to_nearest_int(v: Self::W, w: u32) -> i128;
fn pow10(n: u32) -> Self::W;
fn w_bits() -> u32;
fn ln_table_entry<const SCALE: u32>(w: u32, idx: usize) -> Self::W;
fn exp_table_entry<const SCALE: u32>(w: u32, idx: usize, m: u32) -> Self::W;
fn pi<const SCALE: u32>(w: u32) -> Self::W;
fn half_pi<const SCALE: u32>(w: u32) -> Self::W;
fn deg_per_rad<const SCALE: u32>(w: u32) -> Self::W;
fn rad_per_deg<const SCALE: u32>(w: u32) -> Self::W;
fn sincos_table_entry<const SCALE: u32>(w: u32, idx: usize, m: u32) -> (Self::W, Self::W);
}
#[inline]
fn exp_near_min_pin<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> Option<C::Storage> {
let half = SCALE / 2;
let zero = C::storage_zero();
if half == 0 || raw == zero {
return None;
}
let absr = if raw < zero { zero - raw } else { raw };
let bl = <C::Storage as BigInt>::BITS - absr.leading_zeros();
if ((bl - 1) as u64) * 100_000 >= (half as u64) * 332_193 {
return None;
}
if absr >= crate::consts::pow10::dispatch::<C::Storage>(half) {
return None;
}
let g = C::storage_one(SCALE) + raw; Some(match mode {
RoundingMode::Ceiling => g + <C::Storage as BigInt>::from_i128(1),
_ => g,
})
}
#[inline]
#[must_use]
pub(crate) fn exp_series<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::Wexp as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if raw == C::storage_zero() {
return C::storage_one(SCALE);
}
if let Some(r) = exp_near_min_pin::<C, SCALE>(raw, mode) {
return r;
}
round_to_storage_widening_g::<C::Storage, C::W, C::Wexp>(
C::GUARD,
SCALE,
mode,
true,
C::storage_max(),
C::storage_min(),
|guard| C::exp_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
|guard| {
crate::algos::exp::exp_generic::exp_fixed::<C::Wexp>(
to_work_scaled_g::<C::Storage, C::Wexp>(raw, guard),
SCALE + guard,
)
},
)
}
#[cfg(feature = "_wide-support")]
#[inline]
#[must_use]
pub(crate) fn exp_series_g<C: WideTrigCore, Wk: BigInt, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
Wk::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::Wexp as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
use crate::algos::exp::exp_generic as eg;
if raw == C::storage_zero() {
return C::storage_one(SCALE);
}
if let Some(r) = exp_near_min_pin::<C, SCALE>(raw, mode) {
return r;
}
round_to_storage_widening_g::<C::Storage, Wk, C::Wexp>(
C::GUARD,
SCALE,
mode,
true,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
let v = to_work_scaled_g::<C::Storage, Wk>(raw, guard);
if eg::exp_peak_fits::<Wk>(v, w) {
eg::exp_fixed::<Wk>(v, w)
} else {
eg::resize_or_panic::<C::Wexp, Wk>(eg::exp_fixed::<C::Wexp>(
to_work_scaled_g::<C::Storage, C::Wexp>(raw, guard),
w,
))
}
},
|guard| {
eg::exp_fixed::<C::Wexp>(
to_work_scaled_g::<C::Storage, C::Wexp>(raw, guard),
SCALE + guard,
)
},
)
}
#[inline]
#[must_use]
pub(crate) fn ln_series<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
ln_series_g::<C, C::W, SCALE>(raw, mode)
}
#[inline]
#[must_use]
pub(crate) fn ln_series_g<C: WideTrigCore, Wk: BigInt, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<Wk as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if raw <= C::storage_zero() {
panic!("wide-tier ln: argument must be positive");
}
round_to_storage_directed_g::<C::Storage, Wk>(
C::GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
let ln2 = if w == SCALE + C::GUARD {
crate::consts::ln2_by_scale::<Wk>(w, crate::support::rounding::DEFAULT_ROUNDING_MODE)
} else {
crate::consts::ln2_by_working_scale::<Wk>(
w,
crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
};
crate::algos::exp::exp_generic::ln_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard),
w,
ln2,
)
},
)
}
#[inline]
#[must_use]
pub(crate) fn sin_series<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if let Some(v) = tiny_x_linear_directed::<C::Storage, SCALE>(raw, mode, false) {
return v;
}
let (r, decided) = round_to_storage_directed_decided_g::<C::Storage, C::W>(
C::GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| C::sin_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
);
let r = tiny_x_deep_directed_adjust::<C::Storage, SCALE>(r, decided, raw, mode, true, <C::W as BigInt>::BITS);
adjust_bounded_extremum::<C, SCALE>(r, raw, mode)
}
#[inline]
#[must_use]
pub(crate) fn cos_series<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage {
let r = C::round_to_storage_directed(C::GUARD, SCALE, mode, &mut |guard| {
C::cos_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard)
});
adjust_bounded_extremum::<C, SCALE>(r, raw, mode)
}
#[inline]
pub(crate) fn adjust_bounded_extremum<C: WideTrigCore, const SCALE: u32>(
result: C::Storage,
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage {
if crate::support::rounding::is_nearest_mode(mode) || raw == C::storage_zero() {
return result;
}
let one = C::storage_one(SCALE);
let neg_one = C::storage_zero() - one;
if result == one {
match mode {
RoundingMode::Floor | RoundingMode::Trunc => one - <C::Storage as BigInt>::ONE,
_ => result,
}
} else if result == neg_one {
match mode {
RoundingMode::Ceiling | RoundingMode::Trunc => neg_one + <C::Storage as BigInt>::ONE,
_ => result,
}
} else {
result
}
}
#[inline]
pub(crate) fn tiny_x_linear_directed<St: BigInt, const SCALE: u32>(
raw: St,
mode: RoundingMode,
expanding: bool,
) -> Option<St> {
if crate::support::rounding::is_nearest_mode(mode) || SCALE == 0 {
return None;
}
let zero = <St as BigInt>::ZERO;
if raw == zero {
return None; }
let absr = if raw < zero { zero - raw } else { raw };
let thresh_exp = SCALE - SCALE.div_ceil(3);
if absr > crate::consts::pow10::dispatch::<St>(thresh_exp) {
return None;
}
let one = <St as BigInt>::ONE;
Some(if expanding {
crate::support::rounding::tiny_odd_expanding_directed(raw, zero, one, mode)
} else {
crate::support::rounding::tiny_odd_compressing_directed(raw, zero, one, mode)
})
}
const TINY_X_DEEP_JMAX: u32 = 41;
#[inline]
pub(crate) fn tiny_x_deep_directed_adjust<St: BigInt, const SCALE: u32>(
r: St,
decided: bool,
raw: St,
mode: RoundingMode,
alternating: bool,
work_bits: u32,
) -> St {
if decided || crate::support::rounding::is_nearest_mode(mode) || SCALE == 0 {
return r;
}
let zero = <St as BigInt>::ZERO;
if raw == zero {
return r;
}
let absr = if raw < zero { zero - raw } else { raw };
let digits = dec_digits_g::<St>(absr);
if digits == 0 || digits > SCALE {
return r;
}
let k = SCALE - digits + 1;
if k == 0 {
return r;
}
let j_min = SCALE / k + 1;
let j_star = if j_min % 2 == 1 { j_min } else { j_min + 1 };
if !(5..=TINY_X_DEEP_JMAX).contains(&j_star) {
return r;
}
let reach = (work_bits / 8).saturating_sub(8);
if j_star.saturating_mul(k) <= reach {
return r;
}
let expanding = if alternating { j_star % 4 == 1 } else { true };
let one = <St as BigInt>::ONE;
if expanding {
crate::support::rounding::tiny_odd_expanding_directed(r, zero, one, mode)
} else {
crate::support::rounding::tiny_odd_compressing_directed(r, zero, one, mode)
}
}
#[inline]
pub(crate) fn adjust_ln_near_one<C: WideTrigCore, const SCALE: u32>(
result: C::Storage,
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage {
if crate::support::rounding::is_nearest_mode(mode) {
return result;
}
let one = C::storage_one(SCALE);
if raw == one {
return result; }
let delta = raw - one; if result != delta {
return result; }
match mode {
RoundingMode::Floor => result - <C::Storage as BigInt>::ONE,
RoundingMode::Trunc if raw > one => result - <C::Storage as BigInt>::ONE,
_ => result,
}
}
#[inline]
#[must_use]
pub(crate) fn tan_series<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if raw == C::storage_zero() {
return C::storage_zero(); }
if let Some(v) = tiny_x_linear_directed::<C::Storage, SCALE>(raw, mode, true) {
return v;
}
let w0 = SCALE + C::GUARD;
let (sin0, cos0) = C::sin_cos_fixed::<SCALE>(C::to_work(raw), w0);
if cos0 == C::zero() {
panic!("wide-tier tan: cosine is zero (argument is an odd multiple of pi/2)");
}
let probe = C::div(sin0, cos0, w0);
let extra = crate::algos::trig::near_pole_tan::tan_extra_digits(C::bit_length(probe), w0)
.saturating_sub(C::GUARD);
if extra == 0 {
if let Some(st) = round_to_storage_clear_of_tie_g::<C::Storage, C::W>(
probe, w0, SCALE, mode, C::storage_max(), C::storage_min(),
) {
return st;
}
return tan_walker::<C, SCALE>(raw, C::GUARD, mode);
}
let w = w0 + extra;
let (sin_w, cos_w) = C::sin_cos_fixed::<SCALE>(C::to_work_scaled(raw, C::GUARD + extra), w);
let r = C::div(sin_w, cos_w, w);
if let Some(st) = round_to_storage_clear_of_tie_g::<C::Storage, C::W>(
r, w, SCALE, mode, C::storage_max(), C::storage_min(),
) {
return st;
}
tan_walker::<C, SCALE>(raw, C::GUARD + extra, mode)
}
fn tan_walker<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
base_guard: u32,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let (r, decided) = round_to_storage_directed_decided_g::<C::Storage, C::W>(
base_guard,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
let (s, c) = C::sin_cos_fixed::<SCALE>(C::to_work_scaled(raw, guard), w);
if c == C::zero() {
panic!("wide-tier tan: cosine is zero (argument is an odd multiple of pi/2)");
}
C::div(s, c, w)
},
);
tiny_x_deep_directed_adjust::<C::Storage, SCALE>(r, decided, raw, mode, false, <C::W as BigInt>::BITS)
}
#[inline]
#[must_use]
pub(crate) fn atan_series<C: WideTrigCore, const SCALE: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if let Some(v) = tiny_x_linear_directed::<C::Storage, SCALE>(raw, mode, false) {
return v;
}
let (r, decided) = round_to_storage_directed_decided_g::<C::Storage, C::W>(
C::GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| C::atan_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
);
tiny_x_deep_directed_adjust::<C::Storage, SCALE>(r, decided, raw, mode, true, <C::W as BigInt>::BITS)
}
#[inline]
#[must_use]
pub(crate) fn atan_narrow<C: WideTrigCore, const SCALE: u32, const GUARD: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage {
C::round_to_storage_directed(GUARD, SCALE, mode, &mut |guard| {
C::atan_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard)
})
}
#[cfg(feature = "_wide-support")]
#[inline]
#[must_use]
pub(crate) fn sin_series_g<C: WideTrigCore, Wk: BigInt, const SCALE: u32, const GUARD: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
Wk::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if raw == C::storage_zero() {
return C::storage_zero();
}
if let Some(v) = tiny_x_linear_directed::<C::Storage, SCALE>(raw, mode, false) {
return v;
}
let (r, decided) = round_to_storage_directed_widening_decided_g::<C::Storage, Wk, C::W>(
GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
crate::algos::trig::trig_generic::sin_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard),
w,
pi_at_rung::<Wk>(w, SCALE + GUARD),
)
},
|guard| C::sin_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
);
let r = tiny_x_deep_directed_adjust::<C::Storage, SCALE>(r, decided, raw, mode, true, <C::W as BigInt>::BITS);
adjust_bounded_extremum::<C, SCALE>(r, raw, mode)
}
#[cfg(feature = "_wide-support")]
#[inline]
#[must_use]
pub(crate) fn cos_series_g<C: WideTrigCore, Wk: BigInt, const SCALE: u32, const GUARD: u32>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
Wk::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if raw == C::storage_zero() {
return C::storage_one(SCALE);
}
let r = round_to_storage_directed_widening_g::<C::Storage, Wk, C::W>(
GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
crate::algos::trig::trig_generic::cos_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard),
w,
pi_at_rung::<Wk>(w, SCALE + GUARD),
)
},
|guard| C::cos_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
);
adjust_bounded_extremum::<C, SCALE>(r, raw, mode)
}
#[cfg(feature = "_wide-support")]
#[inline]
#[must_use]
pub(crate) fn tan_series_g<
C: WideTrigCore,
Wk: BigInt,
const SCALE: u32,
const GUARD: u32,
const NEAR_POLE: bool,
const SUB_GUARD: bool,
>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
Wk::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
use crate::algos::exp::exp_generic as eg;
if raw == C::storage_zero() {
return C::storage_zero();
}
if let Some(v) = tiny_x_linear_directed::<C::Storage, SCALE>(raw, mode, true) {
return v;
}
let w0 = SCALE + GUARD;
let (sin0, cos0) = crate::algos::trig::trig_generic::sin_cos_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, GUARD),
w0,
pi_at_rung::<Wk>(w0, w0),
);
if cos0 == eg::zero::<Wk>() {
panic!("wide-tier tan: cosine is zero (argument is an odd multiple of pi/2)");
}
let probe = eg::div::<Wk>(sin0, cos0, w0);
if !NEAR_POLE {
if let Some(st) = round_to_storage_clear_of_tie_g::<C::Storage, Wk>(
probe, w0, SCALE, mode, C::storage_max(), C::storage_min(),
) {
return st;
}
return tan_walker_rung_g::<C, Wk, SCALE>(raw, GUARD, mode);
}
let extra_raw =
crate::algos::trig::near_pole_tan::tan_extra_digits(eg::bit_length::<Wk>(probe), w0);
let extra = if SUB_GUARD { extra_raw.saturating_sub(GUARD) } else { extra_raw };
if extra == 0 {
if let Some(st) = round_to_storage_clear_of_tie_g::<C::Storage, Wk>(
probe, w0, SCALE, mode, C::storage_max(), C::storage_min(),
) {
return st;
}
return tan_walker_rung_g::<C, Wk, SCALE>(raw, GUARD, mode);
}
let w = w0 + extra;
let (sin_w, cos_w) = crate::algos::trig::trig_generic::sin_cos_fixed::<C::W>(
to_work_scaled_g::<C::Storage, C::W>(raw, GUARD + extra),
w,
crate::consts::pi_by_working_scale::<C::W>(
w,
crate::support::rounding::DEFAULT_ROUNDING_MODE,
),
);
let r = eg::div::<C::W>(sin_w, cos_w, w);
if let Some(st) = round_to_storage_clear_of_tie_g::<C::Storage, C::W>(
r, w, SCALE, mode, C::storage_max(), C::storage_min(),
) {
return st;
}
tan_walker::<C, SCALE>(raw, GUARD + extra, mode)
}
#[cfg(feature = "_wide-support")]
fn tan_walker_rung_g<C: WideTrigCore, Wk: BigInt, const SCALE: u32>(
raw: C::Storage,
base_guard: u32,
mode: RoundingMode,
) -> C::Storage
where
Wk::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
use crate::algos::exp::exp_generic as eg;
let base_w = SCALE + base_guard;
let (r, decided) = round_to_storage_directed_widening_decided_g::<C::Storage, Wk, C::W>(
base_guard,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
let (s, c) = crate::algos::trig::trig_generic::sin_cos_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard),
w,
pi_at_rung::<Wk>(w, base_w),
);
if c == eg::zero::<Wk>() {
panic!("wide-tier tan: cosine is zero (argument is an odd multiple of pi/2)");
}
eg::div::<Wk>(s, c, w)
},
|guard| {
let w = SCALE + guard;
let (s, c) = C::sin_cos_fixed::<SCALE>(C::to_work_scaled(raw, guard), w);
if c == C::zero() {
panic!("wide-tier tan: cosine is zero (argument is an odd multiple of pi/2)");
}
C::div(s, c, w)
},
);
tiny_x_deep_directed_adjust::<C::Storage, SCALE>(r, decided, raw, mode, false, <C::W as BigInt>::BITS)
}
#[cfg(feature = "_wide-support")]
#[inline]
#[must_use]
pub(crate) fn atan_series_g<
C: WideTrigCore,
Wk: BigInt,
const SCALE: u32,
const GUARD: u32,
const DIRECTED: bool,
>(
raw: C::Storage,
mode: RoundingMode,
) -> C::Storage
where
Wk::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
<C::W as BigInt>::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if let Some(v) = tiny_x_linear_directed::<C::Storage, SCALE>(raw, mode, false) {
return v;
}
let (r, decided) = if DIRECTED {
round_to_storage_directed_widening_decided_g::<C::Storage, Wk, C::W>(
GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
crate::algos::trig::trig_generic::atan_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard),
w,
pi_at_rung::<Wk>(w, SCALE + GUARD),
)
},
|guard| C::atan_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
)
} else {
let base_w = SCALE + GUARD;
round_to_storage_directed_widening_decided_g::<C::Storage, Wk, C::W>(
GUARD,
SCALE,
mode,
C::storage_max(),
C::storage_min(),
|guard| {
let w = SCALE + guard;
crate::algos::trig::trig_generic::atan_fixed::<Wk>(
to_work_scaled_g::<C::Storage, Wk>(raw, guard),
w,
pi_at_rung::<Wk>(w, base_w),
)
},
|guard| C::atan_fixed::<SCALE>(C::to_work_scaled(raw, guard), SCALE + guard),
)
};
tiny_x_deep_directed_adjust::<C::Storage, SCALE>(r, decided, raw, mode, true, <C::W as BigInt>::BITS)
}
#[cfg(feature = "_wide-support")]
#[inline]
pub(crate) fn pi_at_rung<Wk: BigInt>(w: u32, base_w: u32) -> Wk {
if w == base_w {
crate::consts::pi_by_scale::<Wk>(base_w, crate::support::rounding::DEFAULT_ROUNDING_MODE)
} else {
crate::consts::pi_by_working_scale::<Wk>(
w,
crate::support::rounding::DEFAULT_ROUNDING_MODE,
)
}
}
#[inline]
pub(crate) fn to_work_scaled_g<St: BigInt, S: BigInt>(raw: St, working_digits: u32) -> S
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
crate::algos::exp::exp_generic::pow10::<S>(working_digits) * BigInt::resize_to::<S>(raw)
}
#[inline]
fn narrow_range_checked_g<St: BigInt + Copy, S: BigInt>(signed: S, st_max: St, st_min: St) -> St
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
if <S as BigInt>::LIMBS >= <St as BigInt>::LIMBS {
let max_w = BigInt::resize_to::<S>(st_max);
let min_w = BigInt::resize_to::<S>(st_min);
if signed > max_w || signed < min_w {
panic!("wide-tier strict transcendental: result out of range");
}
}
let neg = signed < <S as BigInt>::ZERO;
let mag = if neg { -signed } else { signed };
let mut buf =
<S::Scratch as crate::int::types::compute_limbs::ComputeLimbs>::single_u64();
crate::algos::exp::exp_generic::unpack_mag(mag, buf.as_mut());
St::from_mag_sign_u64(buf.as_ref(), neg)
}
#[inline]
pub(crate) fn round_to_storage_with_g<St: BigInt + Copy, S: BigInt>(
v: S,
w: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
) -> St
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let shift = w - target;
let rounded = if shift == 0 {
v
} else if shift <= 38 {
crate::algos::support::mg_divide::div_wide_pow10::<S>(v, shift, mode)
} else {
crate::algos::support::rescale::dispatch_wide_pow10::<S>(v, shift, mode)
};
narrow_range_checked_g::<St, S>(rounded, st_max, st_min)
}
const ZIV_RESOLVE_FLOOR_POW10: u32 = 4;
const ZIV_PRECISION_HORIZON: u32 = 1264;
fn dec_digits_g<S: BigInt>(v: S) -> u32 {
let bl = <S as BigInt>::BITS - v.leading_zeros();
let mut d = ((bl as u64 * 30_103) / 100_000) as u32 + 1;
if d > 1 && v < crate::consts::pow10::dispatch::<S>(d - 1) {
d -= 1;
}
d
}
#[allow(clippy::too_many_arguments)]
fn near_min_resolve_g<St: BigInt + Copy, S: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
never_exact: bool,
st_max: St,
st_min: St,
recompute: &mut dyn FnMut(u32) -> S,
) -> (St, bool)
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
use crate::support::rounding::{is_nearest_mode, should_bump, RoundingMode};
let lit = |n: i128| <S as BigInt>::from_i128(n);
let pow10 = |n: u32| crate::consts::pow10::dispatch::<S>(n);
let bit_length = |v: S| -> u32 {
let m = if v < <S as BigInt>::ZERO { -v } else { v };
<S as BigInt>::BITS - m.leading_zeros()
};
let floor = pow10(ZIV_RESOLVE_FLOOR_POW10);
let max_guard_for = |int_digits: u32| -> u32 {
let cap = (<S>::BITS / 8).saturating_sub(int_digits + 8);
cap.saturating_sub(target)
.min((ZIV_PRECISION_HORIZON + int_digits).saturating_sub(target))
.max(base_guard)
};
let int_digits_of = |v: St| -> u32 {
let n = BigInt::resize_to::<S>(v);
let m = if n < lit(0) { -n } else { n };
((bit_length(m) as u64 * 30103 / 100_000) as u32 + 1).saturating_sub(target)
};
let range_check = |signed: S| -> St { narrow_range_checked_g::<St, S>(signed, st_max, st_min) };
let finish = |neg: bool, q: S, bump: bool| -> St {
let q_mag = if bump { q + lit(1) } else { q };
range_check(if neg { -q_mag } else { q_mag })
};
let pos_of = |dist: S, g: u32| -> u32 { target + g - dec_digits_g::<S>(dist) + 1 };
let mut probe = |g: u32| -> (bool, S, S, S) {
let v = recompute(g);
let neg = v < lit(0);
let mag = if neg { -v } else { v };
let divisor = pow10(g);
let (q, rem) = crate::algos::exp::exp_generic::div_rem_exact(mag, divisor);
(neg, q, rem, divisor)
};
if is_nearest_mode(mode) {
let round_half = |neg: bool, q: S, rem: S, divisor: S| -> St {
let half = divisor / lit(2);
let cmp_r = if rem < half {
::core::cmp::Ordering::Less
} else if rem == half {
::core::cmp::Ordering::Equal
} else {
::core::cmp::Ordering::Greater
};
finish(neg, q, should_bump(mode, cmp_r, q.bit(0), !neg))
};
let (neg0, q0, rem0, div0) = probe(base_guard);
let half0 = div0 / lit(2);
let dist0 = if rem0 < half0 { half0 - rem0 } else { rem0 - half0 };
if dist0 > pow10(base_guard) / lit(1000) {
return (round_half(neg0, q0, rem0, div0), true); }
let lo = round_half(neg0, q0, rem0, div0);
let max_guard = max_guard_for(int_digits_of(lo));
let mut pending: Option<(u32, bool)> = if dist0 > floor {
Some((pos_of(dist0, base_guard), rem0 > half0))
} else {
None
};
let mut guard = base_guard;
loop {
if guard >= max_guard {
if let Some((pp, ps)) = pending {
let back = ZIV_RESOLVE_FLOOR_POW10 + 3;
if max_guard > base_guard + back {
let g_c = max_guard - back;
let (neg, q, rem, div) = probe(g_c);
let half = div / lit(2);
let dist = if rem < half { half - rem } else { rem - half };
if dist > floor {
let p = pos_of(dist, g_c);
if (rem > half) == ps && p.abs_diff(pp) <= 1 {
return (round_half(neg, q, rem, div), true);
}
}
}
}
if never_exact {
return (finish(neg0, q0, true), false);
}
return (lo, false);
}
let step = (target + base_guard).max(base_guard);
let next_guard = guard.saturating_add(step).min(max_guard);
let (neg, q, rem, div) = probe(next_guard);
let half = div / lit(2);
let hi_dist = if rem < half { half - rem } else { rem - half };
if hi_dist > floor {
let p = pos_of(hi_dist, next_guard);
let above = rem > half;
if let Some((pp, ps)) = pending {
if above == ps && p.abs_diff(pp) <= 1 {
return (round_half(neg, q, rem, div), true);
}
}
pending = Some((p, above));
}
guard = next_guard;
}
}
let dir_round = |neg: bool, q: S, rem: S| -> St {
let result_positive = !neg;
let residual_present = rem != lit(0) || never_exact;
let bump = residual_present
&& match mode {
RoundingMode::Trunc => false,
RoundingMode::Floor => !result_positive,
RoundingMode::Ceiling => result_positive,
_ => unreachable!(),
};
finish(neg, q, bump)
};
let (neg0, q0, rem0, div0) = probe(base_guard);
let dist0 = if rem0 < div0 - rem0 { rem0 } else { div0 - rem0 };
if dist0 > pow10(base_guard) / lit(1000) {
return (dir_round(neg0, q0, rem0), true); }
let base = dir_round(neg0, q0, rem0);
let max_guard = max_guard_for(int_digits_of(base));
let mut pending: Option<(u32, bool)> = if dist0 > floor {
Some((pos_of(dist0, base_guard), rem0 < div0 - rem0))
} else {
None
};
let mut guard = base_guard;
loop {
if guard >= max_guard {
if let Some((pp, ps)) = pending {
let back = ZIV_RESOLVE_FLOOR_POW10 + 3;
if max_guard > base_guard + back {
let g_c = max_guard - back;
let (neg, q, rem, div) = probe(g_c);
let dist = if rem < div - rem { rem } else { div - rem };
if dist > floor {
let p = pos_of(dist, g_c);
if (rem < div - rem) == ps && p.abs_diff(pp) <= 1 {
return (dir_round(neg, q, rem), true);
}
}
}
}
let q_grid = if rem0 > div0 / lit(2) { q0 + lit(1) } else { q0 };
let tail_bump = never_exact
&& match mode {
RoundingMode::Trunc => false,
RoundingMode::Floor => neg0,
RoundingMode::Ceiling => !neg0,
_ => unreachable!(),
};
return (finish(neg0, q_grid, tail_bump), false);
}
let step = (target + base_guard).max(base_guard);
let next_guard = guard.saturating_add(step).min(max_guard);
let (neg, q, rem, div) = probe(next_guard);
let hi_dist = if rem < div - rem { rem } else { div - rem };
if hi_dist > floor {
let p = pos_of(hi_dist, next_guard);
let above = rem < div - rem;
if let Some((pp, ps)) = pending {
if above == ps && p.abs_diff(pp) <= 1 {
return (dir_round(neg, q, rem), true);
}
}
pending = Some((p, above));
}
guard = next_guard;
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) fn round_to_storage_widening_g<St: BigInt + Copy, S1: BigInt, S2: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
never_exact: bool,
st_max: St,
st_min: St,
mut recompute1: impl FnMut(u32) -> S1,
mut recompute2: impl FnMut(u32) -> S2,
) -> St
where
S1::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
S2::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let (v1, resolved1) = near_min_resolve_g::<St, S1>(
base_guard, target, mode, never_exact, st_max, st_min, &mut recompute1,
);
let int_digits = {
let m = if v1 < <St as BigInt>::ZERO { -v1 } else { v1 };
let bl = <St as BigInt>::BITS - m.leading_zeros();
((bl as u64 * 30103 / 100_000) as u32 + 1).saturating_sub(target)
};
if resolved1 || (<S1>::BITS / 8) >= ZIV_PRECISION_HORIZON + int_digits {
return v1;
}
near_min_resolve_g::<St, S2>(
base_guard, target, mode, never_exact, st_max, st_min, &mut recompute2,
)
.0
}
#[inline]
pub(crate) fn round_to_storage_clear_of_tie_g<St: BigInt + Copy, S: BigInt>(
v: S,
w: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
) -> Option<St>
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
use crate::support::rounding::{is_nearest_mode, should_bump};
let lit = |n: i128| <S as BigInt>::from_i128(n);
let shift = w - target;
if shift == 0 {
return Some(narrow_range_checked_g::<St, S>(v, st_max, st_min));
}
let neg = v < lit(0);
let mag = if neg { -v } else { v };
let divisor = crate::consts::pow10::dispatch::<S>(shift);
let (q, rem) = mag.div_rem(divisor);
let band = if shift >= 3 {
crate::consts::pow10::dispatch::<S>(shift - 3)
} else {
lit(0)
};
let bump = if is_nearest_mode(mode) {
let half = divisor >> 1;
let dist = if rem < half { half - rem } else { rem - half };
if dist <= band {
return None;
}
rem != lit(0)
&& should_bump(mode, rem.cmp(&(divisor - rem)), q.bit(0), !neg)
} else {
let dist = if rem < divisor - rem { rem } else { divisor - rem };
if dist <= band {
return None;
}
rem != lit(0)
&& match mode {
RoundingMode::Trunc => false,
RoundingMode::Floor => neg,
RoundingMode::Ceiling => !neg,
_ => unreachable!(),
}
};
let q_mag = if bump { q + lit(1) } else { q };
let signed = if neg { -q_mag } else { q_mag };
Some(narrow_range_checked_g::<St, S>(signed, st_max, st_min))
}
#[inline]
pub(crate) fn round_to_storage_directed_g<St: BigInt + Copy, S: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute: impl FnMut(u32) -> S,
) -> St
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
round_to_storage_directed_impl_g::<St, S>(
base_guard, target, mode, false, false, st_max, st_min, &mut recompute,
)
.0
}
#[inline]
pub(crate) fn round_to_storage_directed_decided_g<St: BigInt + Copy, S: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute: impl FnMut(u32) -> S,
) -> (St, bool)
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
round_to_storage_directed_impl_g::<St, S>(
base_guard, target, mode, false, false, st_max, st_min, &mut recompute,
)
}
#[inline]
pub(crate) fn round_to_storage_directed_never_exact_g<St: BigInt + Copy, S: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute: impl FnMut(u32) -> S,
) -> St
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
round_to_storage_directed_impl_g::<St, S>(
base_guard, target, mode, false, true, st_max, st_min, &mut recompute,
)
.0
}
#[inline]
pub(crate) fn round_to_storage_directed_near_special_g<St: BigInt + Copy, S: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute: impl FnMut(u32) -> S,
) -> St
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
round_to_storage_directed_impl_g::<St, S>(
base_guard, target, mode, true, false, st_max, st_min, &mut recompute,
)
.0
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) fn round_to_storage_directed_widening_g<St: BigInt + Copy, S1: BigInt, S2: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute1: impl FnMut(u32) -> S1,
mut recompute2: impl FnMut(u32) -> S2,
) -> St
where
S1::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
S2::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let (v, resolved) = round_to_storage_directed_impl_g::<St, S1>(
base_guard, target, mode, false, false, st_max, st_min, &mut recompute1,
);
if resolved || <S1 as BigInt>::BITS >= <S2 as BigInt>::BITS {
return v;
}
round_to_storage_directed_impl_g::<St, S2>(
base_guard, target, mode, false, false, st_max, st_min, &mut recompute2,
)
.0
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) fn round_to_storage_directed_widening_decided_g<St: BigInt + Copy, S1: BigInt, S2: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute1: impl FnMut(u32) -> S1,
mut recompute2: impl FnMut(u32) -> S2,
) -> (St, bool)
where
S1::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
S2::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let (v, resolved) = round_to_storage_directed_impl_g::<St, S1>(
base_guard, target, mode, false, false, st_max, st_min, &mut recompute1,
);
if resolved || <S1 as BigInt>::BITS >= <S2 as BigInt>::BITS {
return (v, resolved);
}
round_to_storage_directed_impl_g::<St, S2>(
base_guard, target, mode, false, false, st_max, st_min, &mut recompute2,
)
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) fn round_to_storage_directed_near_special_widening_g<
St: BigInt + Copy,
S1: BigInt,
S2: BigInt,
>(
base_guard: u32,
target: u32,
mode: RoundingMode,
st_max: St,
st_min: St,
mut recompute1: impl FnMut(u32) -> S1,
mut recompute2: impl FnMut(u32) -> S2,
) -> St
where
S1::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
S2::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let (v, resolved) = round_to_storage_directed_impl_g::<St, S1>(
base_guard, target, mode, true, false, st_max, st_min, &mut recompute1,
);
if resolved || <S1 as BigInt>::BITS >= <S2 as BigInt>::BITS {
return v;
}
round_to_storage_directed_impl_g::<St, S2>(
base_guard, target, mode, true, false, st_max, st_min, &mut recompute2,
)
.0
}
#[allow(clippy::too_many_arguments)]
fn round_to_storage_directed_impl_g<St: BigInt + Copy, S: BigInt>(
base_guard: u32,
target: u32,
mode: RoundingMode,
force_confirm: bool,
never_exact: bool,
st_max: St,
st_min: St,
recompute: &mut dyn FnMut(u32) -> S,
) -> (St, bool)
where
S::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
use crate::support::rounding::{is_nearest_mode, RoundingMode};
let lit = |n: i128| <S as BigInt>::from_i128(n);
let pow10 = |n: u32| crate::consts::pow10::dispatch::<S>(n);
let bit_length = |v: S| -> u32 {
let m = if v < <S as BigInt>::ZERO { -v } else { v };
<S as BigInt>::BITS - m.leading_zeros()
};
let floor = pow10(ZIV_RESOLVE_FLOOR_POW10);
let band_of = |g: u32| -> S { if g >= 3 { pow10(g - 3) } else { lit(0) } };
if is_nearest_mode(mode) {
let mut nearest_narrow = |guard: u32| -> (St, S, S) {
let v = recompute(guard);
let neg = v < lit(0);
let mag = if neg { -v } else { v };
let divisor = pow10(guard);
let (q, rem) = crate::algos::exp::exp_generic::div_rem_exact(mag, divisor);
let q_mag = if rem != lit(0) {
let comp = divisor - rem;
let bump = crate::support::rounding::should_bump(
mode,
rem.cmp(&comp),
q.bit(0),
!neg,
);
if bump { q + lit(1) } else { q }
} else {
q
};
let signed = if neg { -q_mag } else { q_mag };
let narrowed = narrow_range_checked_g::<St, S>(signed, st_max, st_min);
let half = divisor >> 1;
let dist_half = if rem < half { half - rem } else { rem - half };
(narrowed, dist_half, divisor)
};
let (lo, dist0, _divisor0) = nearest_narrow(base_guard);
if !force_confirm && dist0 > band_of(base_guard) {
return (lo, true);
}
let int_digits = {
let n = BigInt::resize_to::<S>(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 = (<S>::BITS / 8).saturating_sub(int_digits + 8);
let max_guard = cap_digits
.saturating_sub(target)
.min(ZIV_PRECISION_HORIZON.saturating_sub(target))
.max(base_guard);
let mut guard = base_guard;
let mut best = lo;
loop {
if guard >= max_guard {
return (if force_confirm { best } else { lo }, false);
}
let step = (target + base_guard).max(base_guard);
let unclamped = guard.saturating_add(step);
let next_guard = unclamped.min(max_guard);
let tainted = unclamped > max_guard;
let (hi, hi_dist, _) = nearest_narrow(next_guard);
if force_confirm {
if hi == best {
return (best, !tainted);
}
} else if hi_dist > floor {
return (hi, !tainted);
}
guard = next_guard;
best = hi;
}
}
let mut directed_narrow = |guard: u32| -> (S, S, S) {
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) = crate::algos::exp::exp_generic::div_rem_exact(mag, divisor);
let result_positive = !neg;
let residual_present = rem != lit(0) || never_exact;
let bump = residual_present
&& 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 = band_of(base_guard);
let near_grid = force_confirm || dist0 <= band0;
let (signed, decided) = if !near_grid {
(lo, true)
} else {
let base = lo;
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 = (<S>::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 last_resolved = false;
loop {
if guard >= max_guard {
break (if force_confirm || last_resolved { lo } else { base }, false);
}
let step = (target + base_guard).max(base_guard);
let unclamped = guard.saturating_add(step);
let next_guard = unclamped.min(max_guard);
let tainted = unclamped > max_guard;
let (hi, hi_dist, _hi_div) = directed_narrow(next_guard);
let resolved = hi_dist == lit(0) || hi_dist > floor;
if hi == lo && resolved {
break (hi, !tainted);
}
guard = next_guard;
lo = hi;
last_resolved = resolved;
}
};
(narrow_range_checked_g::<St, S>(signed, st_max, st_min), decided)
}
#[cfg(test)]
mod directed_walker_contract {
use super::*;
use crate::int::types::Int;
use crate::support::rounding::RoundingMode;
type S = Int<24>;
type St = Int<2>;
const BASE_GUARD: u32 = 30;
const TARGET: u32 = 17;
const ALL_MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Ceiling,
RoundingMode::Floor,
RoundingMode::Trunc,
];
fn run(mode: RoundingMode, recompute: impl FnMut(u32) -> S) -> i128 {
round_to_storage_directed_never_exact_g::<St, S>(
BASE_GUARD,
TARGET,
mode,
St::MAX,
St::MIN,
recompute,
)
.as_i128()
}
const POISON_FROM: u32 = 150;
#[test]
fn sub_resolution_positive_resolves_correctly_despite_poisoned_tail() {
for mode in ALL_MODES {
let got = run(mode, |g| {
let v = <S as BigInt>::from_i128(995)
* crate::consts::pow10::dispatch::<S>(g - BASE_GUARD);
if g >= POISON_FROM { -v } else { v }
});
let want = if mode == RoundingMode::Ceiling { 1 } else { 0 };
assert_eq!(got, want, "sub-resolution positive, mode={mode:?}");
}
}
#[test]
fn unresolved_cap_returns_clean_base_not_deepest_probe() {
for mode in ALL_MODES {
let got = run(mode, |g| {
let v = <S as BigInt>::from_i128(5);
if g >= POISON_FROM { -v } else { v }
});
let want = if mode == RoundingMode::Ceiling { 1 } else { 0 };
assert_eq!(got, want, "noise-scale residual at cap, mode={mode:?}");
}
}
#[test]
fn deciding_digit_first_visible_at_cap_probe_is_trusted() {
type Rung = Int<16>;
for (mode, want) in [
(RoundingMode::Ceiling, 2_i128),
(RoundingMode::Floor, 1),
(RoundingMode::Trunc, 1),
(RoundingMode::HalfToEven, 1),
] {
let got = round_to_storage_directed_g::<St, Rung>(
BASE_GUARD,
TARGET_ASIN,
mode,
St::MAX,
St::MIN,
|g| {
let one = crate::consts::pow10::dispatch::<Rung>(g);
if g >= 80 {
one + <Rung as BigInt>::from_i128(1667)
* crate::consts::pow10::dispatch::<Rung>(g - 80)
} else {
one
}
},
)
.as_i128();
assert_eq!(got, want, "late-visible deciding digit, mode={mode:?}");
}
}
const TARGET_ASIN: u32 = 38;
}
#[cfg(all(test, any(feature = "d153", feature = "wide")))]
mod tiny_x_directed_pins {
use crate::int::types::{traits::BigInt, Int};
use crate::support::rounding::RoundingMode::{
Ceiling, Floor, HalfAwayFromZero, HalfToEven, HalfTowardZero, Trunc,
};
const ULP: Int<8> = <Int<8> as BigInt>::ONE;
macro_rules! pin {
($scale:literal, $kneg:literal, $f:ident, $expanding:expr, $label:literal) => {{
let r = Int::<8>::from_i128(3)
* crate::consts::pow10::dispatch::<Int<8>>($scale - $kneg);
let x = crate::D::<Int<8>, $scale>(r);
let nr = -r;
let nx = crate::D::<Int<8>, $scale>(nr);
for m in [HalfToEven, HalfAwayFromZero, HalfTowardZero] {
assert_eq!(x.$f(m).0, r, "{} (+x) {:?}", $label, m);
assert_eq!(nx.$f(m).0, nr, "{} (−x) {:?}", $label, m);
}
if $expanding {
assert_eq!(x.$f(Ceiling).0, r + ULP, "{} (+x) Ceiling", $label);
assert_eq!(x.$f(Floor).0, r, "{} (+x) Floor", $label);
assert_eq!(x.$f(Trunc).0, r, "{} (+x) Trunc", $label);
assert_eq!(nx.$f(Floor).0, nr - ULP, "{} (−x) Floor", $label);
assert_eq!(nx.$f(Ceiling).0, nr, "{} (−x) Ceiling", $label);
assert_eq!(nx.$f(Trunc).0, nr, "{} (−x) Trunc", $label);
} else {
assert_eq!(x.$f(Floor).0, r - ULP, "{} (+x) Floor", $label);
assert_eq!(x.$f(Trunc).0, r - ULP, "{} (+x) Trunc", $label);
assert_eq!(x.$f(Ceiling).0, r, "{} (+x) Ceiling", $label);
assert_eq!(nx.$f(Ceiling).0, nr + ULP, "{} (−x) Ceiling", $label);
assert_eq!(nx.$f(Trunc).0, nr + ULP, "{} (−x) Trunc", $label);
assert_eq!(nx.$f(Floor).0, nr, "{} (−x) Floor", $label);
}
}};
}
#[test]
fn tan_expanding_d153_tang_and_series_bands() {
pin!(76, 60, tan_strict_with, true, "tan s76");
pin!(152, 120, tan_strict_with, true, "tan s152");
}
#[test]
fn sin_compressing_d153_tang_and_series_bands() {
pin!(76, 60, sin_strict_with, false, "sin s76");
pin!(152, 120, sin_strict_with, false, "sin s152");
}
#[test]
fn atan_compressing_d153_series_band() {
pin!(152, 120, atan_strict_with, false, "atan s152");
}
#[test]
fn asin_expanding_d153_series_band() {
pin!(152, 120, asin_strict_with, true, "asin s152");
}
}
#[cfg(all(test, any(feature = "d616", feature = "x-wide")))]
mod tiny_x_deep_directed_pins {
use crate::int::types::{traits::BigInt, Int};
use crate::support::rounding::RoundingMode::{
Ceiling, Floor, HalfAwayFromZero, HalfToEven, HalfTowardZero, Trunc,
};
const ULP: Int<32> = <Int<32> as BigInt>::ONE;
macro_rules! pin_deep {
($scale:literal, $kneg:literal, $f:ident, $expanding:expr, $label:literal) => {{
let r = Int::<32>::from_i128(3)
* crate::consts::pow10::dispatch::<Int<32>>($scale - $kneg);
let x = crate::D::<Int<32>, $scale>(r);
let nx = crate::D::<Int<32>, $scale>(-r);
let g = x.$f(HalfToEven).0; let ng = nx.$f(HalfToEven).0;
assert_ne!(g, r, "{}: expected the DEEP band (G != x)", $label);
for m in [HalfToEven, HalfAwayFromZero, HalfTowardZero] {
assert_eq!(x.$f(m).0, g, "{} (+x) {:?}", $label, m);
assert_eq!(nx.$f(m).0, ng, "{} (−x) {:?}", $label, m);
}
if $expanding {
assert_eq!(x.$f(Ceiling).0, g + ULP, "{} (+x) Ceiling", $label);
assert_eq!(x.$f(Floor).0, g, "{} (+x) Floor", $label);
assert_eq!(x.$f(Trunc).0, g, "{} (+x) Trunc", $label);
assert_eq!(nx.$f(Floor).0, ng - ULP, "{} (−x) Floor", $label);
assert_eq!(nx.$f(Ceiling).0, ng, "{} (−x) Ceiling", $label);
assert_eq!(nx.$f(Trunc).0, ng, "{} (−x) Trunc", $label);
} else {
assert_eq!(x.$f(Floor).0, g - ULP, "{} (+x) Floor", $label);
assert_eq!(x.$f(Trunc).0, g - ULP, "{} (+x) Trunc", $label);
assert_eq!(x.$f(Ceiling).0, g, "{} (+x) Ceiling", $label);
assert_eq!(nx.$f(Ceiling).0, ng + ULP, "{} (−x) Ceiling", $label);
assert_eq!(nx.$f(Trunc).0, ng + ULP, "{} (−x) Trunc", $label);
assert_eq!(nx.$f(Floor).0, ng, "{} (−x) Floor", $label);
}
}};
}
#[test]
fn sin_atan_compressing_d616_s615_deep() {
pin_deep!(615, 120, sin_strict_with, false, "sin s615");
pin_deep!(615, 120, atan_strict_with, false, "atan s615");
}
#[test]
fn tan_asin_expanding_d616_s615_deep() {
pin_deep!(615, 120, tan_strict_with, true, "tan s615");
pin_deep!(615, 120, asin_strict_with, true, "asin s615");
}
}