use crate::core_type::D38;
impl<const SCALE: u32> D38<SCALE> {
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn sin(self) -> Self {
self.sin_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn cos(self) -> Self {
self.cos_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn tan(self) -> Self {
self.tan_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn asin(self) -> Self {
self.asin_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn acos(self) -> Self {
self.acos_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn atan(self) -> Self {
self.atan_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn atan2(self, other: Self) -> Self {
self.atan2_strict(other)
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn sinh(self) -> Self {
self.sinh_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn cosh(self) -> Self {
self.cosh_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn tanh(self) -> Self {
self.tanh_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn asinh(self) -> Self {
self.asinh_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn acosh(self) -> Self {
self.acosh_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn atanh(self) -> Self {
self.atanh_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn to_degrees(self) -> Self {
self.to_degrees_strict()
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[inline]
#[must_use]
pub fn to_radians(self) -> Self {
self.to_radians_strict()
}
#[inline]
#[must_use]
pub fn sin_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let raw = sin_fixed(to_fixed(self.0), w)
.round_to_i128(w, SCALE)
.expect("D38::sin: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn cos_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let arg = to_fixed(self.0).add(wide_half_pi(w));
let raw = sin_fixed(arg, w)
.round_to_i128(w, SCALE)
.expect("D38::cos: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn tan_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let v = to_fixed(self.0);
let sin_w = sin_fixed(v, w);
let cos_w = sin_fixed(v.add(wide_half_pi(w)), w);
assert!(!cos_w.is_zero(), "D38::tan: cosine is zero (argument is an odd multiple of pi/2)");
let raw = sin_w
.div(cos_w, w)
.round_to_i128(w, SCALE)
.expect("D38::tan: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn atan_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let raw = atan_fixed(to_fixed(self.0), w)
.round_to_i128(w, SCALE)
.expect("D38::atan: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn asin_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(self.0);
let abs_v = Fixed { negative: false, mag: v.mag };
assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::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 };
let raw = hp
.round_to_i128(w, SCALE)
.expect("D38::asin: result out of range");
return Self::from_bits(raw);
}
let denom = one_w.sub(v.mul(v, w)).sqrt(w);
let raw = atan_fixed(v.div(denom, w), w)
.round_to_i128(w, SCALE)
.expect("D38::asin: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn acos_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(self.0);
let abs_v = Fixed { negative: false, mag: v.mag };
assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::acos: 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 denom = one_w.sub(v.mul(v, w)).sqrt(w);
atan_fixed(v.div(denom, w), w)
};
let raw = wide_half_pi(w)
.sub(asin_w)
.round_to_i128(w, SCALE)
.expect("D38::acos: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn atan2_strict(self, other: Self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let y = to_fixed(self.0);
let x = to_fixed(other.0);
let result_w = if x.is_zero() {
if self.0 > 0 {
wide_half_pi(w)
} else if self.0 < 0 {
wide_half_pi(w).neg()
} else {
crate::d_w128_kernels::Fixed::ZERO
}
} else {
let base = atan_fixed(y.div(x, w), w);
if !x.negative {
base
} else if !y.negative {
base.add(wide_pi(w))
} else {
base.sub(wide_pi(w))
}
};
let raw = result_w
.round_to_i128(w, SCALE)
.expect("D38::atan2: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn sinh_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let v = to_fixed(self.0);
let ex = crate::log_exp_strict::exp_fixed(v, w);
let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
let raw = ex
.sub(enx)
.halve()
.round_to_i128(w, SCALE)
.expect("D38::sinh: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn cosh_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let v = to_fixed(self.0);
let ex = crate::log_exp_strict::exp_fixed(v, w);
let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
let raw = ex
.add(enx)
.halve()
.round_to_i128(w, SCALE)
.expect("D38::cosh: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn tanh_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let v = to_fixed(self.0);
let ex = crate::log_exp_strict::exp_fixed(v, w);
let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
let raw = ex
.sub(enx)
.div(ex.add(enx), w)
.round_to_i128(w, SCALE)
.expect("D38::tanh: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn asinh_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
if self.0 == 0 {
return Self::ZERO;
}
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(self.0);
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);
crate::log_exp_strict::ln_fixed(ax, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
} else {
let root = ax.mul(ax, w).add(one_w).sqrt(w);
crate::log_exp_strict::ln_fixed(ax.add(root), w)
};
let signed = if self.0 < 0 { inner.neg() } else { inner };
let raw = signed
.round_to_i128(w, SCALE)
.expect("D38::asinh: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn acosh_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(self.0);
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);
crate::log_exp_strict::ln_fixed(v, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
} else {
let root = v.mul(v, w).sub(one_w).sqrt(w);
crate::log_exp_strict::ln_fixed(v.add(root), w)
};
let raw = inner
.round_to_i128(w, SCALE)
.expect("D38::acosh: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn atanh_strict(self) -> Self {
use crate::d_w128_kernels::Fixed;
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
let v = to_fixed(self.0);
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);
let raw = crate::log_exp_strict::ln_fixed(ratio, w)
.halve()
.round_to_i128(w, SCALE)
.expect("D38::atanh: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn to_degrees_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let raw = to_fixed(self.0)
.mul_u128(180)
.div(wide_pi(w), w)
.round_to_i128(w, SCALE)
.expect("D38::to_degrees: result out of range");
Self::from_bits(raw)
}
#[inline]
#[must_use]
pub fn to_radians_strict(self) -> Self {
let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
let raw = to_fixed(self.0)
.mul(wide_pi(w), w)
.div_small(180)
.round_to_i128(w, SCALE)
.expect("D38::to_radians: result out of range");
Self::from_bits(raw)
}
}
fn wide_pi(w: u32) -> crate::d_w128_kernels::Fixed {
crate::d_w128_kernels::Fixed::from_decimal_split(
31_415_926_535_897_932_384_626_433_832_795_u128,
2_884_197_169_399_375_105_820_974_944_592_u128,
)
.rescale_down(63, w)
}
fn wide_tau(w: u32) -> crate::d_w128_kernels::Fixed {
wide_pi(w).double()
}
fn wide_half_pi(w: u32) -> crate::d_w128_kernels::Fixed {
wide_pi(w).halve()
}
fn to_fixed(raw: i128) -> crate::d_w128_kernels::Fixed {
use crate::d_w128_kernels::Fixed;
let m = Fixed::from_u128_mag(raw.unsigned_abs(), false)
.mul_u128(10u128.pow(crate::log_exp_strict::STRICT_GUARD));
if raw < 0 {
m.neg()
} else {
m
}
}
fn sin_taylor(r: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::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 sin_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
use crate::d_w128_kernels::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: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::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
}
fn atan_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
use crate::d_w128_kernels::Fixed;
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;
}
let halvings: u32 = 3;
for _ in 0..halvings {
let x2 = x.mul(x, w);
let denom = one_w.add(one_w.add(x2).sqrt(w));
x = x.div(denom, w);
}
let mut result = atan_taylor(x, w);
result = result.shl(halvings);
if add_half_pi {
result = wide_half_pi(w).sub(result);
}
if sign {
result.neg()
} else {
result
}
}
#[cfg(test)]
mod tests {
use crate::consts::DecimalConsts;
use crate::core_type::D38s12;
const TWO_LSB: i128 = 2;
const FOUR_LSB: i128 = 4;
const ANGLE_TOLERANCE_LSB: i128 = 32;
fn within_lsb(actual: D38s12, expected: D38s12, lsb: i128) -> bool {
let diff = (actual.to_bits() - expected.to_bits()).abs();
diff <= lsb
}
#[cfg(all(feature = "strict", not(feature = "fast")))]
#[test]
fn strict_trig_family_matches_f64() {
use crate::core_type::D38;
macro_rules! check {
($name:literal, $raw:expr, $strict:expr, $f64expr:expr) => {{
let strict: i128 = $strict;
let v = $raw as f64 / 1e9;
let reference = ($f64expr(v) * 1e9).round() as i128;
assert!(
(strict - reference).abs() <= 2,
concat!($name, "({}) = {}, f64 reference {}"),
$raw,
strict,
reference
);
}};
}
for &raw in &[
-7_000_000_000_i128, -1_000_000_000, -100_000_000, 1,
500_000_000, 1_000_000_000, 1_570_796_327, 3_000_000_000,
6_283_185_307, 12_000_000_000,
] {
let x = D38::<9>::from_bits(raw);
check!("sin", raw, x.sin_strict().to_bits(), f64::sin);
check!("cos", raw, x.cos_strict().to_bits(), f64::cos);
check!("atan", raw, x.atan_strict().to_bits(), f64::atan);
check!("sinh", raw, x.sinh_strict().to_bits(), f64::sinh);
check!("cosh", raw, x.cosh_strict().to_bits(), f64::cosh);
check!("tanh", raw, x.tanh_strict().to_bits(), f64::tanh);
check!("asinh", raw, x.asinh_strict().to_bits(), f64::asinh);
}
for &raw in &[
-1_000_000_000_i128, -700_000_000, -100_000_000, 0,
250_000_000, 500_000_000, 999_999_999,
] {
let x = D38::<9>::from_bits(raw);
check!("asin", raw, x.asin_strict().to_bits(), f64::asin);
check!("acos", raw, x.acos_strict().to_bits(), f64::acos);
}
for &raw in &[-900_000_000_i128, -300_000_000, 1, 300_000_000, 900_000_000] {
let x = D38::<9>::from_bits(raw);
check!("atanh", raw, x.atanh_strict().to_bits(), f64::atanh);
}
for &raw in &[1_000_000_000_i128, 1_500_000_000, 3_000_000_000, 50_000_000_000] {
let x = D38::<9>::from_bits(raw);
check!("acosh", raw, x.acosh_strict().to_bits(), f64::acosh);
}
for &raw in &[-1_000_000_000_i128, 1, 500_000_000, 1_000_000_000, 1_400_000_000] {
let x = D38::<9>::from_bits(raw);
check!("tan", raw, x.tan_strict().to_bits(), f64::tan);
}
}
#[test]
fn sin_zero_is_zero() {
assert_eq!(D38s12::ZERO.sin(), D38s12::ZERO);
}
#[test]
fn cos_zero_is_one() {
assert_eq!(D38s12::ZERO.cos(), D38s12::ONE);
}
#[test]
fn tan_zero_is_zero() {
assert_eq!(D38s12::ZERO.tan(), D38s12::ZERO);
}
#[test]
fn sin_squared_plus_cos_squared_is_one() {
for raw in [
1_234_567_890_123_i128, -2_345_678_901_234_i128, 500_000_000_000_i128, -500_000_000_000_i128, 4_567_891_234_567_i128, ] {
let x = D38s12::from_bits(raw);
let s = x.sin();
let c = x.cos();
let sum = (s * s) + (c * c);
assert!(
within_lsb(sum, D38s12::ONE, FOUR_LSB),
"sin^2 + cos^2 != 1 for raw={raw}: got bits {} (delta {})",
sum.to_bits(),
(sum.to_bits() - D38s12::ONE.to_bits()).abs(),
);
}
}
#[test]
fn asin_zero_is_zero() {
assert_eq!(D38s12::ZERO.asin(), D38s12::ZERO);
}
#[test]
fn acos_one_is_zero() {
assert_eq!(D38s12::ONE.acos(), D38s12::ZERO);
}
#[test]
fn acos_zero_is_half_pi() {
let result = D38s12::ZERO.acos();
assert!(
within_lsb(result, D38s12::half_pi(), FOUR_LSB),
"acos(0) bits {}, half_pi bits {}",
result.to_bits(),
D38s12::half_pi().to_bits(),
);
}
#[test]
fn atan_zero_is_zero() {
assert_eq!(D38s12::ZERO.atan(), D38s12::ZERO);
}
#[test]
fn asin_of_sin_round_trip() {
for raw in [
123_456_789_012_i128, -123_456_789_012_i128, 456_789_012_345_i128, -456_789_012_345_i128, 1_234_567_890_123_i128, -1_234_567_890_123_i128, ] {
let x = D38s12::from_bits(raw);
let recovered = x.sin().asin();
assert!(
within_lsb(recovered, x, FOUR_LSB),
"asin(sin(x)) != x for raw={raw}: got bits {} (delta {})",
recovered.to_bits(),
(recovered.to_bits() - x.to_bits()).abs(),
);
}
}
#[test]
fn atan2_first_quadrant_diagonal() {
let one = D38s12::ONE;
let result = one.atan2(one);
assert!(
within_lsb(result, D38s12::quarter_pi(), TWO_LSB),
"atan2(1, 1) bits {}, quarter_pi bits {}",
result.to_bits(),
D38s12::quarter_pi().to_bits(),
);
}
#[test]
fn atan2_third_quadrant_diagonal() {
let neg_one = -D38s12::ONE;
let result = neg_one.atan2(neg_one);
let three = D38s12::from_int(3);
let expected = -(D38s12::quarter_pi() * three);
assert!(
within_lsb(result, expected, TWO_LSB),
"atan2(-1, -1) bits {}, expected -3pi/4 bits {}",
result.to_bits(),
expected.to_bits(),
);
}
#[test]
fn atan2_second_quadrant_diagonal() {
let one = D38s12::ONE;
let neg_one = -D38s12::ONE;
let result = one.atan2(neg_one);
let three = D38s12::from_int(3);
let expected = D38s12::quarter_pi() * three;
assert!(
within_lsb(result, expected, TWO_LSB),
"atan2(1, -1) bits {}, expected 3pi/4 bits {}",
result.to_bits(),
expected.to_bits(),
);
}
#[test]
fn atan2_fourth_quadrant_diagonal() {
let one = D38s12::ONE;
let neg_one = -D38s12::ONE;
let result = neg_one.atan2(one);
let expected = -D38s12::quarter_pi();
assert!(
within_lsb(result, expected, TWO_LSB),
"atan2(-1, 1) bits {}, expected -pi/4 bits {}",
result.to_bits(),
expected.to_bits(),
);
}
#[test]
fn atan2_positive_x_axis_is_zero() {
let zero = D38s12::ZERO;
let one = D38s12::ONE;
assert_eq!(zero.atan2(one), D38s12::ZERO);
}
#[test]
fn sinh_zero_is_zero() {
assert_eq!(D38s12::ZERO.sinh(), D38s12::ZERO);
}
#[test]
fn cosh_zero_is_one() {
assert_eq!(D38s12::ZERO.cosh(), D38s12::ONE);
}
#[test]
fn tanh_zero_is_zero() {
assert_eq!(D38s12::ZERO.tanh(), D38s12::ZERO);
}
#[test]
fn asinh_zero_is_zero() {
assert_eq!(D38s12::ZERO.asinh(), D38s12::ZERO);
}
#[test]
fn acosh_one_is_zero() {
assert_eq!(D38s12::ONE.acosh(), D38s12::ZERO);
}
#[test]
fn atanh_zero_is_zero() {
assert_eq!(D38s12::ZERO.atanh(), D38s12::ZERO);
}
#[test]
fn cosh_squared_minus_sinh_squared_is_one() {
if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
for raw in [
500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -1_234_567_890_123_i128, 2_500_000_000_000_i128, ] {
let x = D38s12::from_bits(raw);
let ch = x.cosh();
let sh = x.sinh();
let diff = (ch * ch) - (sh * sh);
assert!(
within_lsb(diff, D38s12::ONE, FOUR_LSB),
"cosh^2 - sinh^2 != 1 for raw={raw}: got bits {} (delta {})",
diff.to_bits(),
(diff.to_bits() - D38s12::ONE.to_bits()).abs(),
);
}
}
#[test]
fn to_degrees_pi_is_180() {
if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
let pi = D38s12::pi();
let result = pi.to_degrees();
let expected = D38s12::from_int(180);
assert!(
within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
"to_degrees(pi) bits {}, expected 180 bits {} (delta {})",
result.to_bits(),
expected.to_bits(),
(result.to_bits() - expected.to_bits()).abs(),
);
}
#[test]
fn to_radians_180_is_pi() {
let one_eighty = D38s12::from_int(180);
let result = one_eighty.to_radians();
let expected = D38s12::pi();
assert!(
within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
"to_radians(180) bits {}, expected pi bits {} (delta {})",
result.to_bits(),
expected.to_bits(),
(result.to_bits() - expected.to_bits()).abs(),
);
}
#[test]
fn to_degrees_zero_is_zero() {
assert_eq!(D38s12::ZERO.to_degrees(), D38s12::ZERO);
}
#[test]
fn to_radians_zero_is_zero() {
assert_eq!(D38s12::ZERO.to_radians(), D38s12::ZERO);
}
#[test]
fn to_radians_to_degrees_round_trip() {
for raw in [
500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
let x = D38s12::from_bits(raw);
let recovered = x.to_degrees().to_radians();
assert!(
within_lsb(recovered, x, FOUR_LSB),
"to_radians(to_degrees(x)) != x for raw={raw}: got bits {} (delta {})",
recovered.to_bits(),
(recovered.to_bits() - x.to_bits()).abs(),
);
}
}
#[test]
fn to_degrees_half_pi_is_90() {
if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
let result = D38s12::half_pi().to_degrees();
let expected = D38s12::from_int(90);
assert!(
within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
"to_degrees(half_pi) bits {}, expected 90 bits {} (delta {})",
result.to_bits(),
expected.to_bits(),
(result.to_bits() - expected.to_bits()).abs(),
);
}
#[test]
fn to_degrees_quarter_pi_is_45() {
let result = D38s12::quarter_pi().to_degrees();
let expected = D38s12::from_int(45);
assert!(
within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
"to_degrees(quarter_pi) bits {}, expected 45 bits {} (delta {})",
result.to_bits(),
expected.to_bits(),
(result.to_bits() - expected.to_bits()).abs(),
);
}
#[test]
fn tan_matches_sin_over_cos() {
for raw in [
500_000_000_000_i128, -500_000_000_000_i128, 1_000_000_000_000_i128, -1_000_000_000_000_i128, 123_456_789_012_i128, ] {
let x = D38s12::from_bits(raw);
let t = x.tan();
let sc = x.sin() / x.cos();
assert!(
within_lsb(t, sc, FOUR_LSB),
"tan(x) != sin/cos for raw={raw}: tan bits {}, sin/cos bits {}",
t.to_bits(),
sc.to_bits(),
);
}
}
#[test]
fn tanh_matches_sinh_over_cosh() {
for raw in [
500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
let x = D38s12::from_bits(raw);
let t = x.tanh();
let sc = x.sinh() / x.cosh();
assert!(
within_lsb(t, sc, FOUR_LSB),
"tanh(x) != sinh/cosh for raw={raw}: tanh bits {}, sinh/cosh bits {}",
t.to_bits(),
sc.to_bits(),
);
}
}
}