1use crate::core_type::D38;
49
50impl<const SCALE: u32> D38<SCALE> {
51#[cfg(all(feature = "strict", not(feature = "fast")))]
52 #[inline]
55 #[must_use]
56 pub fn sin(self) -> Self {
57 self.sin_strict()
58 }
59#[cfg(all(feature = "strict", not(feature = "fast")))]
60
61 #[inline]
64 #[must_use]
65 pub fn cos(self) -> Self {
66 self.cos_strict()
67 }
68#[cfg(all(feature = "strict", not(feature = "fast")))]
69
70 #[inline]
73 #[must_use]
74 pub fn tan(self) -> Self {
75 self.tan_strict()
76 }
77#[cfg(all(feature = "strict", not(feature = "fast")))]
78
79 #[inline]
82 #[must_use]
83 pub fn asin(self) -> Self {
84 self.asin_strict()
85 }
86#[cfg(all(feature = "strict", not(feature = "fast")))]
87
88 #[inline]
91 #[must_use]
92 pub fn acos(self) -> Self {
93 self.acos_strict()
94 }
95#[cfg(all(feature = "strict", not(feature = "fast")))]
96
97 #[inline]
100 #[must_use]
101 pub fn atan(self) -> Self {
102 self.atan_strict()
103 }
104#[cfg(all(feature = "strict", not(feature = "fast")))]
105
106 #[inline]
110 #[must_use]
111 pub fn atan2(self, other: Self) -> Self {
112 self.atan2_strict(other)
113 }
114#[cfg(all(feature = "strict", not(feature = "fast")))]
115
116 #[inline]
119 #[must_use]
120 pub fn sinh(self) -> Self {
121 self.sinh_strict()
122 }
123#[cfg(all(feature = "strict", not(feature = "fast")))]
124
125 #[inline]
128 #[must_use]
129 pub fn cosh(self) -> Self {
130 self.cosh_strict()
131 }
132#[cfg(all(feature = "strict", not(feature = "fast")))]
133
134 #[inline]
137 #[must_use]
138 pub fn tanh(self) -> Self {
139 self.tanh_strict()
140 }
141#[cfg(all(feature = "strict", not(feature = "fast")))]
142
143 #[inline]
146 #[must_use]
147 pub fn asinh(self) -> Self {
148 self.asinh_strict()
149 }
150#[cfg(all(feature = "strict", not(feature = "fast")))]
151
152 #[inline]
155 #[must_use]
156 pub fn acosh(self) -> Self {
157 self.acosh_strict()
158 }
159#[cfg(all(feature = "strict", not(feature = "fast")))]
160
161 #[inline]
164 #[must_use]
165 pub fn atanh(self) -> Self {
166 self.atanh_strict()
167 }
168#[cfg(all(feature = "strict", not(feature = "fast")))]
169
170 #[inline]
173 #[must_use]
174 pub fn to_degrees(self) -> Self {
175 self.to_degrees_strict()
176 }
177#[cfg(all(feature = "strict", not(feature = "fast")))]
178
179 #[inline]
182 #[must_use]
183 pub fn to_radians(self) -> Self {
184 self.to_radians_strict()
185 }
186 #[inline]
189 #[must_use]
190 pub fn sin_strict(self) -> Self {
191 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
192 let raw = sin_fixed(to_fixed(self.0), w)
193 .round_to_i128(w, SCALE)
194 .expect("D38::sin: result out of range");
195 Self::from_bits(raw)
196 }
197
198 #[inline]
201 #[must_use]
202 pub fn cos_strict(self) -> Self {
203 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
204 let arg = to_fixed(self.0).add(wide_half_pi(w));
205 let raw = sin_fixed(arg, w)
206 .round_to_i128(w, SCALE)
207 .expect("D38::cos: result out of range");
208 Self::from_bits(raw)
209 }
210
211 #[inline]
219 #[must_use]
220 pub fn tan_strict(self) -> Self {
221 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
222 let v = to_fixed(self.0);
223 let sin_w = sin_fixed(v, w);
224 let cos_w = sin_fixed(v.add(wide_half_pi(w)), w);
225 assert!(!cos_w.is_zero(), "D38::tan: cosine is zero (argument is an odd multiple of pi/2)");
226 let raw = sin_w
227 .div(cos_w, w)
228 .round_to_i128(w, SCALE)
229 .expect("D38::tan: result out of range");
230 Self::from_bits(raw)
231 }
232
233 #[inline]
236 #[must_use]
237 pub fn atan_strict(self) -> Self {
238 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
239 let raw = atan_fixed(to_fixed(self.0), w)
240 .round_to_i128(w, SCALE)
241 .expect("D38::atan: result out of range");
242 Self::from_bits(raw)
243 }
244
245 #[inline]
254 #[must_use]
255 pub fn asin_strict(self) -> Self {
256 use crate::d_w128_kernels::Fixed;
257 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
258 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
259 let v = to_fixed(self.0);
260 let abs_v = Fixed { negative: false, mag: v.mag };
261 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::asin: argument out of domain [-1, 1]");
262 if abs_v == one_w {
263 let hp = wide_half_pi(w);
264 let hp = if v.negative { hp.neg() } else { hp };
265 let raw = hp
266 .round_to_i128(w, SCALE)
267 .expect("D38::asin: result out of range");
268 return Self::from_bits(raw);
269 }
270 let half_w = one_w.halve();
273 let asin_w = if !abs_v.ge_mag(half_w) {
274 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
276 atan_fixed(v.div(denom, w), w)
277 } else {
278 let inner = one_w.sub(abs_v).halve();
280 let inner_sqrt = inner.sqrt(w);
281 let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
282 let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
283 let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
284 if v.negative { result_abs.neg() } else { result_abs }
285 };
286 let raw = asin_w
287 .round_to_i128(w, SCALE)
288 .expect("D38::asin: result out of range");
289 Self::from_bits(raw)
290 }
291
292 #[inline]
299 #[must_use]
300 pub fn acos_strict(self) -> Self {
301 use crate::d_w128_kernels::Fixed;
302 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
303 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
304 let v = to_fixed(self.0);
305 let abs_v = Fixed { negative: false, mag: v.mag };
306 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::acos: argument out of domain [-1, 1]");
307 let half_w = one_w.halve();
309 let asin_w = if abs_v == one_w {
310 let hp = wide_half_pi(w);
311 if v.negative { hp.neg() } else { hp }
312 } else if !abs_v.ge_mag(half_w) {
313 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
314 atan_fixed(v.div(denom, w), w)
315 } else {
316 let inner = one_w.sub(abs_v).halve();
317 let inner_sqrt = inner.sqrt(w);
318 let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
319 let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
320 let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
321 if v.negative { result_abs.neg() } else { result_abs }
322 };
323 let raw = wide_half_pi(w)
324 .sub(asin_w)
325 .round_to_i128(w, SCALE)
326 .expect("D38::acos: result out of range");
327 Self::from_bits(raw)
328 }
329
330 #[inline]
334 #[must_use]
335 pub fn atan2_strict(self, other: Self) -> Self {
336 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
337 let y = to_fixed(self.0);
338 let x = to_fixed(other.0);
339 let result_w = if x.is_zero() {
340 if self.0 > 0 {
341 wide_half_pi(w)
342 } else if self.0 < 0 {
343 wide_half_pi(w).neg()
344 } else {
345 crate::d_w128_kernels::Fixed::ZERO
346 }
347 } else {
348 let abs_y_ge_abs_x = y.ge_mag(x);
353 let base = if !abs_y_ge_abs_x {
354 atan_fixed(y.div(x, w), w)
355 } else {
356 let inv = atan_fixed(x.div(y, w), w);
357 let hp = wide_half_pi(w);
358 let same_sign = y.negative == x.negative;
360 if same_sign { hp.sub(inv) } else { hp.neg().sub(inv) }
361 };
362 if !x.negative {
363 base
364 } else if !y.negative {
365 base.add(wide_pi(w))
366 } else {
367 base.sub(wide_pi(w))
368 }
369 };
370 let raw = result_w
371 .round_to_i128(w, SCALE)
372 .expect("D38::atan2: result out of range");
373 Self::from_bits(raw)
374 }
375
376 #[inline]
380 #[must_use]
381 pub fn sinh_strict(self) -> Self {
382 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
383 let v = to_fixed(self.0);
384 let ex = crate::log_exp_strict::exp_fixed(v, w);
385 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
386 let raw = ex
387 .sub(enx)
388 .halve()
389 .round_to_i128(w, SCALE)
390 .expect("D38::sinh: result out of range");
391 Self::from_bits(raw)
392 }
393
394 #[inline]
397 #[must_use]
398 pub fn cosh_strict(self) -> Self {
399 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
400 let v = to_fixed(self.0);
401 let ex = crate::log_exp_strict::exp_fixed(v, w);
402 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
403 let raw = ex
404 .add(enx)
405 .halve()
406 .round_to_i128(w, SCALE)
407 .expect("D38::cosh: result out of range");
408 Self::from_bits(raw)
409 }
410
411 #[inline]
415 #[must_use]
416 pub fn tanh_strict(self) -> Self {
417 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
418 let v = to_fixed(self.0);
419 let ex = crate::log_exp_strict::exp_fixed(v, w);
420 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
421 let raw = ex
422 .sub(enx)
423 .div(ex.add(enx), w)
424 .round_to_i128(w, SCALE)
425 .expect("D38::tanh: result out of range");
426 Self::from_bits(raw)
427 }
428
429 #[inline]
435 #[must_use]
436 pub fn asinh_strict(self) -> Self {
437 use crate::d_w128_kernels::Fixed;
438 if self.0 == 0 {
439 return Self::ZERO;
440 }
441 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
442 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
443 let v = to_fixed(self.0);
444 let ax = Fixed { negative: false, mag: v.mag };
445 let inner = if ax.ge_mag(one_w) {
446 let inv = one_w.div(ax, w);
448 let root = one_w.add(inv.mul(inv, w)).sqrt(w);
449 crate::log_exp_strict::ln_fixed(ax, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
450 } else {
451 let root = ax.mul(ax, w).add(one_w).sqrt(w);
453 crate::log_exp_strict::ln_fixed(ax.add(root), w)
454 };
455 let signed = if self.0 < 0 { inner.neg() } else { inner };
456 let raw = signed
457 .round_to_i128(w, SCALE)
458 .expect("D38::asinh: result out of range");
459 Self::from_bits(raw)
460 }
461
462 #[inline]
471 #[must_use]
472 pub fn acosh_strict(self) -> Self {
473 use crate::d_w128_kernels::Fixed;
474 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
475 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
476 let v = to_fixed(self.0);
477 assert!(!v.negative && v.ge_mag(one_w), "D38::acosh: argument must be >= 1");
478 let two_w = one_w.double();
479 let inner = if v.ge_mag(two_w) {
480 let inv = one_w.div(v, w);
482 let root = one_w.sub(inv.mul(inv, w)).sqrt(w);
483 crate::log_exp_strict::ln_fixed(v, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
484 } else {
485 let root = v.mul(v, w).sub(one_w).sqrt(w);
487 crate::log_exp_strict::ln_fixed(v.add(root), w)
488 };
489 let raw = inner
490 .round_to_i128(w, SCALE)
491 .expect("D38::acosh: result out of range");
492 Self::from_bits(raw)
493 }
494
495 #[inline]
503 #[must_use]
504 pub fn atanh_strict(self) -> Self {
505 use crate::d_w128_kernels::Fixed;
506 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
507 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
508 let v = to_fixed(self.0);
509 let ax = Fixed { negative: false, mag: v.mag };
510 assert!(!ax.ge_mag(one_w), "D38::atanh: argument out of domain (-1, 1)");
511 let ratio = one_w.add(v).div(one_w.sub(v), w);
513 let raw = crate::log_exp_strict::ln_fixed(ratio, w)
514 .halve()
515 .round_to_i128(w, SCALE)
516 .expect("D38::atanh: result out of range");
517 Self::from_bits(raw)
518 }
519
520 #[inline]
524 #[must_use]
525 pub fn to_degrees_strict(self) -> Self {
526 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
527 let raw = to_fixed(self.0)
528 .mul_u128(180)
529 .div(wide_pi(w), w)
530 .round_to_i128(w, SCALE)
531 .expect("D38::to_degrees: result out of range");
532 Self::from_bits(raw)
533 }
534
535 #[inline]
538 #[must_use]
539 pub fn to_radians_strict(self) -> Self {
540 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
541 let raw = to_fixed(self.0)
542 .mul(wide_pi(w), w)
543 .div_small(180)
544 .round_to_i128(w, SCALE)
545 .expect("D38::to_radians: result out of range");
546 Self::from_bits(raw)
547 }
548}
549
550
551fn wide_pi(w: u32) -> crate::d_w128_kernels::Fixed {
595 debug_assert!(w <= 75, "wide_pi: working scale {w} exceeds embedded 75-digit π");
596 let words = crate::consts::PI_RAW.0;
599 let pi_at_75 = crate::d_w128_kernels::Fixed {
600 negative: false,
601 mag: [
602 (words[0] as u128) | ((words[1] as u128) << 64),
603 (words[2] as u128) | ((words[3] as u128) << 64),
604 ],
605 };
606 if w == 75 {
607 pi_at_75
608 } else {
609 pi_at_75.rescale_down(75, w)
610 }
611}
612
613fn wide_tau(w: u32) -> crate::d_w128_kernels::Fixed {
615 wide_pi(w).double()
616}
617
618fn wide_half_pi(w: u32) -> crate::d_w128_kernels::Fixed {
620 wide_pi(w).halve()
621}
622
623fn to_fixed(raw: i128) -> crate::d_w128_kernels::Fixed {
626 use crate::d_w128_kernels::Fixed;
627 let m = Fixed::from_u128_mag(raw.unsigned_abs(), false)
628 .mul_u128(10u128.pow(crate::log_exp_strict::STRICT_GUARD));
629 if raw < 0 {
630 m.neg()
631 } else {
632 m
633 }
634}
635
636fn sin_taylor(r: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
639 let r2 = r.mul(r, w);
640 let mut sum = r;
641 let mut term = r; let mut k: u128 = 1;
643 loop {
644 term = term.mul(r2, w).div_small((2 * k) * (2 * k + 1));
646 if term.is_zero() {
647 break;
648 }
649 if k % 2 == 1 {
650 sum = sum.sub(term);
651 } else {
652 sum = sum.add(term);
653 }
654 k += 1;
655 if k > 200 {
656 break;
657 }
658 }
659 sum
660}
661
662fn sin_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
668 use crate::d_w128_kernels::Fixed;
669 let tau = wide_tau(w);
670 let pi = wide_pi(w);
671 let half_pi = wide_half_pi(w);
672
673 let q = v_w.div(tau, w).round_to_nearest_int(w);
675 let q_tau = if q >= 0 {
676 tau.mul_u128(q as u128)
677 } else {
678 tau.mul_u128((-q) as u128).neg()
679 };
680 let r = v_w.sub(q_tau);
681
682 let sign = r.negative;
684 let abs_r = Fixed { negative: false, mag: r.mag };
685 let reduced = if abs_r.ge_mag(half_pi) {
686 pi.sub(abs_r)
687 } else {
688 abs_r
689 };
690 let s = sin_taylor(reduced, w);
691 if sign {
692 s.neg()
693 } else {
694 s
695 }
696}
697
698fn atan_taylor(x: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
701 let x2 = x.mul(x, w);
702 let mut sum = x;
703 let mut term = x; let mut k: u128 = 1;
705 loop {
706 term = term.mul(x2, w);
707 let contrib = term.div_small(2 * k + 1);
708 if contrib.is_zero() {
709 break;
710 }
711 if k % 2 == 1 {
712 sum = sum.sub(contrib);
713 } else {
714 sum = sum.add(contrib);
715 }
716 k += 1;
717 if k > 300 {
718 break;
719 }
720 }
721 sum
722}
723
724fn atan_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
731 use crate::d_w128_kernels::Fixed;
732 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
733 let sign = v_w.negative;
734 let mut x = Fixed { negative: false, mag: v_w.mag };
735 let mut add_half_pi = false;
736 if x.ge_mag(one_w) && x != one_w {
737 x = one_w.div(x, w); add_half_pi = true;
739 }
740 let halvings: u32 = 3;
742 for _ in 0..halvings {
743 let x2 = x.mul(x, w);
744 let denom = one_w.add(one_w.add(x2).sqrt(w));
745 x = x.div(denom, w);
746 }
747 let mut result = atan_taylor(x, w);
748 result = result.shl(halvings);
749 if add_half_pi {
750 result = wide_half_pi(w).sub(result);
751 }
752 if sign {
753 result.neg()
754 } else {
755 result
756 }
757}
758
759#[cfg(test)]
760mod tests {
761 use crate::consts::DecimalConsts;
762 use crate::core_type::D38s12;
763
764 const TWO_LSB: i128 = 2;
770
771 const FOUR_LSB: i128 = 4;
775
776 const ANGLE_TOLERANCE_LSB: i128 = 32;
784
785 fn within_lsb(actual: D38s12, expected: D38s12, lsb: i128) -> bool {
786 let diff = (actual.to_bits() - expected.to_bits()).abs();
787 diff <= lsb
788 }
789
790 #[cfg(all(feature = "strict", not(feature = "fast")))]
799 #[test]
800 fn strict_trig_family_matches_f64() {
801 use crate::core_type::D38;
802 macro_rules! check {
803 ($name:literal, $raw:expr, $strict:expr, $f64expr:expr) => {{
804 let strict: i128 = $strict;
805 let v = $raw as f64 / 1e9;
806 let reference = ($f64expr(v) * 1e9).round() as i128;
807 assert!(
808 (strict - reference).abs() <= 2,
809 concat!($name, "({}) = {}, f64 reference {}"),
810 $raw,
811 strict,
812 reference
813 );
814 }};
815 }
816 for &raw in &[
818 -7_000_000_000_i128, -1_000_000_000, -100_000_000, 1,
819 500_000_000, 1_000_000_000, 1_570_796_327, 3_000_000_000,
820 6_283_185_307, 12_000_000_000,
821 ] {
822 let x = D38::<9>::from_bits(raw);
823 check!("sin", raw, x.sin_strict().to_bits(), f64::sin);
824 check!("cos", raw, x.cos_strict().to_bits(), f64::cos);
825 check!("atan", raw, x.atan_strict().to_bits(), f64::atan);
826 check!("sinh", raw, x.sinh_strict().to_bits(), f64::sinh);
827 check!("cosh", raw, x.cosh_strict().to_bits(), f64::cosh);
828 check!("tanh", raw, x.tanh_strict().to_bits(), f64::tanh);
829 check!("asinh", raw, x.asinh_strict().to_bits(), f64::asinh);
830 }
831 for &raw in &[
833 -1_000_000_000_i128, -700_000_000, -100_000_000, 0,
834 250_000_000, 500_000_000, 999_999_999,
835 ] {
836 let x = D38::<9>::from_bits(raw);
837 check!("asin", raw, x.asin_strict().to_bits(), f64::asin);
838 check!("acos", raw, x.acos_strict().to_bits(), f64::acos);
839 }
840 for &raw in &[-900_000_000_i128, -300_000_000, 1, 300_000_000, 900_000_000] {
842 let x = D38::<9>::from_bits(raw);
843 check!("atanh", raw, x.atanh_strict().to_bits(), f64::atanh);
844 }
845 for &raw in &[1_000_000_000_i128, 1_500_000_000, 3_000_000_000, 50_000_000_000] {
847 let x = D38::<9>::from_bits(raw);
848 check!("acosh", raw, x.acosh_strict().to_bits(), f64::acosh);
849 }
850 for &raw in &[-1_000_000_000_i128, 1, 500_000_000, 1_000_000_000, 1_400_000_000] {
852 let x = D38::<9>::from_bits(raw);
853 check!("tan", raw, x.tan_strict().to_bits(), f64::tan);
854 }
855 }
856
857 #[test]
859 fn sin_zero_is_zero() {
860 assert_eq!(D38s12::ZERO.sin(), D38s12::ZERO);
861 }
862
863 #[cfg(all(feature = "strict", not(feature = "fast")))]
874 #[test]
875 fn sin_one_correct_past_63_digit_pi_window() {
876 use crate::core_type::D38;
877 let expected_35: i128 = 84_147_098_480_789_650_665_250_232_163_029_900;
880 let expected_37: i128 =
883 8_414_709_848_078_965_066_525_023_216_302_989_996;
884
885 let got_35 = D38::<35>::ONE.sin_strict().to_bits();
886 assert!(
887 (got_35 - expected_35).abs() <= 1,
888 "sin(1) @ D38<35>: got {got_35}, expected {expected_35}"
889 );
890
891 let got_37 = D38::<37>::ONE.sin_strict().to_bits();
892 assert!(
893 (got_37 - expected_37).abs() <= 1,
894 "sin(1) @ D38<37>: got {got_37}, expected {expected_37}"
895 );
896 }
897
898 #[test]
900 fn cos_zero_is_one() {
901 assert_eq!(D38s12::ZERO.cos(), D38s12::ONE);
902 }
903
904 #[test]
906 fn tan_zero_is_zero() {
907 assert_eq!(D38s12::ZERO.tan(), D38s12::ZERO);
908 }
909
910 #[test]
914 fn sin_squared_plus_cos_squared_is_one() {
915 for raw in [
916 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, ] {
922 let x = D38s12::from_bits(raw);
923 let s = x.sin();
924 let c = x.cos();
925 let sum = (s * s) + (c * c);
926 assert!(
927 within_lsb(sum, D38s12::ONE, FOUR_LSB),
928 "sin^2 + cos^2 != 1 for raw={raw}: got bits {} (delta {})",
929 sum.to_bits(),
930 (sum.to_bits() - D38s12::ONE.to_bits()).abs(),
931 );
932 }
933 }
934
935 #[test]
939 fn asin_zero_is_zero() {
940 assert_eq!(D38s12::ZERO.asin(), D38s12::ZERO);
941 }
942
943 #[test]
945 fn acos_one_is_zero() {
946 assert_eq!(D38s12::ONE.acos(), D38s12::ZERO);
947 }
948
949 #[test]
951 fn acos_zero_is_half_pi() {
952 let result = D38s12::ZERO.acos();
953 assert!(
954 within_lsb(result, D38s12::half_pi(), FOUR_LSB),
955 "acos(0) bits {}, half_pi bits {}",
956 result.to_bits(),
957 D38s12::half_pi().to_bits(),
958 );
959 }
960
961 #[test]
963 fn atan_zero_is_zero() {
964 assert_eq!(D38s12::ZERO.atan(), D38s12::ZERO);
965 }
966
967 #[test]
970 fn asin_of_sin_round_trip() {
971 for raw in [
972 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, ] {
979 let x = D38s12::from_bits(raw);
980 let recovered = x.sin().asin();
981 assert!(
982 within_lsb(recovered, x, FOUR_LSB),
983 "asin(sin(x)) != x for raw={raw}: got bits {} (delta {})",
984 recovered.to_bits(),
985 (recovered.to_bits() - x.to_bits()).abs(),
986 );
987 }
988 }
989
990 #[test]
994 fn atan2_first_quadrant_diagonal() {
995 let one = D38s12::ONE;
996 let result = one.atan2(one);
997 assert!(
998 within_lsb(result, D38s12::quarter_pi(), TWO_LSB),
999 "atan2(1, 1) bits {}, quarter_pi bits {}",
1000 result.to_bits(),
1001 D38s12::quarter_pi().to_bits(),
1002 );
1003 }
1004
1005 #[test]
1007 fn atan2_third_quadrant_diagonal() {
1008 let neg_one = -D38s12::ONE;
1009 let result = neg_one.atan2(neg_one);
1010 let three = D38s12::from_int(3);
1011 let expected = -(D38s12::quarter_pi() * three);
1012 assert!(
1013 within_lsb(result, expected, TWO_LSB),
1014 "atan2(-1, -1) bits {}, expected -3pi/4 bits {}",
1015 result.to_bits(),
1016 expected.to_bits(),
1017 );
1018 }
1019
1020 #[test]
1022 fn atan2_second_quadrant_diagonal() {
1023 let one = D38s12::ONE;
1024 let neg_one = -D38s12::ONE;
1025 let result = one.atan2(neg_one);
1026 let three = D38s12::from_int(3);
1027 let expected = D38s12::quarter_pi() * three;
1028 assert!(
1029 within_lsb(result, expected, TWO_LSB),
1030 "atan2(1, -1) bits {}, expected 3pi/4 bits {}",
1031 result.to_bits(),
1032 expected.to_bits(),
1033 );
1034 }
1035
1036 #[test]
1038 fn atan2_fourth_quadrant_diagonal() {
1039 let one = D38s12::ONE;
1040 let neg_one = -D38s12::ONE;
1041 let result = neg_one.atan2(one);
1042 let expected = -D38s12::quarter_pi();
1043 assert!(
1044 within_lsb(result, expected, TWO_LSB),
1045 "atan2(-1, 1) bits {}, expected -pi/4 bits {}",
1046 result.to_bits(),
1047 expected.to_bits(),
1048 );
1049 }
1050
1051 #[test]
1053 fn atan2_positive_x_axis_is_zero() {
1054 let zero = D38s12::ZERO;
1055 let one = D38s12::ONE;
1056 assert_eq!(zero.atan2(one), D38s12::ZERO);
1057 }
1058
1059 #[test]
1063 fn sinh_zero_is_zero() {
1064 assert_eq!(D38s12::ZERO.sinh(), D38s12::ZERO);
1065 }
1066
1067 #[test]
1069 fn cosh_zero_is_one() {
1070 assert_eq!(D38s12::ZERO.cosh(), D38s12::ONE);
1071 }
1072
1073 #[test]
1075 fn tanh_zero_is_zero() {
1076 assert_eq!(D38s12::ZERO.tanh(), D38s12::ZERO);
1077 }
1078
1079 #[test]
1081 fn asinh_zero_is_zero() {
1082 assert_eq!(D38s12::ZERO.asinh(), D38s12::ZERO);
1083 }
1084
1085 #[test]
1087 fn acosh_one_is_zero() {
1088 assert_eq!(D38s12::ONE.acosh(), D38s12::ZERO);
1089 }
1090
1091 #[test]
1093 fn atanh_zero_is_zero() {
1094 assert_eq!(D38s12::ZERO.atanh(), D38s12::ZERO);
1095 }
1096
1097 #[test]
1100 fn cosh_squared_minus_sinh_squared_is_one() {
1101 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1102 for raw in [
1103 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, ] {
1109 let x = D38s12::from_bits(raw);
1110 let ch = x.cosh();
1111 let sh = x.sinh();
1112 let diff = (ch * ch) - (sh * sh);
1113 assert!(
1114 within_lsb(diff, D38s12::ONE, FOUR_LSB),
1115 "cosh^2 - sinh^2 != 1 for raw={raw}: got bits {} (delta {})",
1116 diff.to_bits(),
1117 (diff.to_bits() - D38s12::ONE.to_bits()).abs(),
1118 );
1119 }
1120 }
1121
1122 #[test]
1128 fn to_degrees_pi_is_180() {
1129 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1130 let pi = D38s12::pi();
1131 let result = pi.to_degrees();
1132 let expected = D38s12::from_int(180);
1133 assert!(
1134 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1135 "to_degrees(pi) bits {}, expected 180 bits {} (delta {})",
1136 result.to_bits(),
1137 expected.to_bits(),
1138 (result.to_bits() - expected.to_bits()).abs(),
1139 );
1140 }
1141
1142 #[test]
1144 fn to_radians_180_is_pi() {
1145 let one_eighty = D38s12::from_int(180);
1146 let result = one_eighty.to_radians();
1147 let expected = D38s12::pi();
1148 assert!(
1149 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1150 "to_radians(180) bits {}, expected pi bits {} (delta {})",
1151 result.to_bits(),
1152 expected.to_bits(),
1153 (result.to_bits() - expected.to_bits()).abs(),
1154 );
1155 }
1156
1157 #[test]
1159 fn to_degrees_zero_is_zero() {
1160 assert_eq!(D38s12::ZERO.to_degrees(), D38s12::ZERO);
1161 }
1162
1163 #[test]
1165 fn to_radians_zero_is_zero() {
1166 assert_eq!(D38s12::ZERO.to_radians(), D38s12::ZERO);
1167 }
1168
1169 #[test]
1172 fn to_radians_to_degrees_round_trip() {
1173 for raw in [
1174 500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
1179 let x = D38s12::from_bits(raw);
1180 let recovered = x.to_degrees().to_radians();
1181 assert!(
1182 within_lsb(recovered, x, FOUR_LSB),
1183 "to_radians(to_degrees(x)) != x for raw={raw}: got bits {} (delta {})",
1184 recovered.to_bits(),
1185 (recovered.to_bits() - x.to_bits()).abs(),
1186 );
1187 }
1188 }
1189
1190 #[test]
1192 fn to_degrees_half_pi_is_90() {
1193 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1194 let result = D38s12::half_pi().to_degrees();
1195 let expected = D38s12::from_int(90);
1196 assert!(
1197 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1198 "to_degrees(half_pi) bits {}, expected 90 bits {} (delta {})",
1199 result.to_bits(),
1200 expected.to_bits(),
1201 (result.to_bits() - expected.to_bits()).abs(),
1202 );
1203 }
1204
1205 #[test]
1207 fn to_degrees_quarter_pi_is_45() {
1208 let result = D38s12::quarter_pi().to_degrees();
1209 let expected = D38s12::from_int(45);
1210 assert!(
1211 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1212 "to_degrees(quarter_pi) bits {}, expected 45 bits {} (delta {})",
1213 result.to_bits(),
1214 expected.to_bits(),
1215 (result.to_bits() - expected.to_bits()).abs(),
1216 );
1217 }
1218
1219 #[test]
1224 fn tan_matches_sin_over_cos() {
1225 for raw in [
1226 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, ] {
1232 let x = D38s12::from_bits(raw);
1233 let t = x.tan();
1234 let sc = x.sin() / x.cos();
1235 assert!(
1236 within_lsb(t, sc, FOUR_LSB),
1237 "tan(x) != sin/cos for raw={raw}: tan bits {}, sin/cos bits {}",
1238 t.to_bits(),
1239 sc.to_bits(),
1240 );
1241 }
1242 }
1243
1244 #[test]
1247 fn tanh_matches_sinh_over_cosh() {
1248 for raw in [
1249 500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
1254 let x = D38s12::from_bits(raw);
1255 let t = x.tanh();
1256 let sc = x.sinh() / x.cosh();
1257 assert!(
1258 within_lsb(t, sc, FOUR_LSB),
1259 "tanh(x) != sinh/cosh for raw={raw}: tanh bits {}, sinh/cosh bits {}",
1260 t.to_bits(),
1261 sc.to_bits(),
1262 );
1263 }
1264 }
1265}
1266