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);
265 let hp = if v.negative { hp.neg() } else { hp };
266 let raw = hp
267 .round_to_i128(w, SCALE)
268 .expect("D38::asin: result out of range");
269 return Self::from_bits(raw);
270 }
271 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
273 let raw = atan_fixed(v.div(denom, w), w)
274 .round_to_i128(w, SCALE)
275 .expect("D38::asin: result out of range");
276 Self::from_bits(raw)
277 }
278
279 #[inline]
286 #[must_use]
287 pub fn acos_strict(self) -> Self {
288 use crate::d_w128_kernels::Fixed;
289 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
290 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
291 let v = to_fixed(self.0);
292 let abs_v = Fixed { negative: false, mag: v.mag };
293 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::acos: argument out of domain [-1, 1]");
294 let asin_w = if abs_v == one_w {
296 let hp = wide_half_pi(w);
297 if v.negative {
298 hp.neg()
299 } else {
300 hp
301 }
302 } else {
303 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
304 atan_fixed(v.div(denom, w), w)
305 };
306 let raw = wide_half_pi(w)
307 .sub(asin_w)
308 .round_to_i128(w, SCALE)
309 .expect("D38::acos: result out of range");
310 Self::from_bits(raw)
311 }
312
313 #[inline]
317 #[must_use]
318 pub fn atan2_strict(self, other: Self) -> Self {
319 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
320 let y = to_fixed(self.0);
321 let x = to_fixed(other.0);
322 let result_w = if x.is_zero() {
323 if self.0 > 0 {
324 wide_half_pi(w)
325 } else if self.0 < 0 {
326 wide_half_pi(w).neg()
327 } else {
328 crate::d_w128_kernels::Fixed::ZERO
329 }
330 } else {
331 let base = atan_fixed(y.div(x, w), w);
332 if !x.negative {
333 base
334 } else if !y.negative {
335 base.add(wide_pi(w))
336 } else {
337 base.sub(wide_pi(w))
338 }
339 };
340 let raw = result_w
341 .round_to_i128(w, SCALE)
342 .expect("D38::atan2: result out of range");
343 Self::from_bits(raw)
344 }
345
346 #[inline]
350 #[must_use]
351 pub fn sinh_strict(self) -> Self {
352 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
353 let v = to_fixed(self.0);
354 let ex = crate::log_exp_strict::exp_fixed(v, w);
355 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
356 let raw = ex
357 .sub(enx)
358 .halve()
359 .round_to_i128(w, SCALE)
360 .expect("D38::sinh: result out of range");
361 Self::from_bits(raw)
362 }
363
364 #[inline]
367 #[must_use]
368 pub fn cosh_strict(self) -> Self {
369 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
370 let v = to_fixed(self.0);
371 let ex = crate::log_exp_strict::exp_fixed(v, w);
372 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
373 let raw = ex
374 .add(enx)
375 .halve()
376 .round_to_i128(w, SCALE)
377 .expect("D38::cosh: result out of range");
378 Self::from_bits(raw)
379 }
380
381 #[inline]
385 #[must_use]
386 pub fn tanh_strict(self) -> Self {
387 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
388 let v = to_fixed(self.0);
389 let ex = crate::log_exp_strict::exp_fixed(v, w);
390 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
391 let raw = ex
392 .sub(enx)
393 .div(ex.add(enx), w)
394 .round_to_i128(w, SCALE)
395 .expect("D38::tanh: result out of range");
396 Self::from_bits(raw)
397 }
398
399 #[inline]
405 #[must_use]
406 pub fn asinh_strict(self) -> Self {
407 use crate::d_w128_kernels::Fixed;
408 if self.0 == 0 {
409 return Self::ZERO;
410 }
411 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
412 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
413 let v = to_fixed(self.0);
414 let ax = Fixed { negative: false, mag: v.mag };
415 let inner = if ax.ge_mag(one_w) {
416 let inv = one_w.div(ax, w);
418 let root = one_w.add(inv.mul(inv, w)).sqrt(w);
419 crate::log_exp_strict::ln_fixed(ax, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
420 } else {
421 let root = ax.mul(ax, w).add(one_w).sqrt(w);
423 crate::log_exp_strict::ln_fixed(ax.add(root), w)
424 };
425 let signed = if self.0 < 0 { inner.neg() } else { inner };
426 let raw = signed
427 .round_to_i128(w, SCALE)
428 .expect("D38::asinh: result out of range");
429 Self::from_bits(raw)
430 }
431
432 #[inline]
441 #[must_use]
442 pub fn acosh_strict(self) -> Self {
443 use crate::d_w128_kernels::Fixed;
444 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
445 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
446 let v = to_fixed(self.0);
447 assert!(!v.negative && v.ge_mag(one_w), "D38::acosh: argument must be >= 1");
448 let two_w = one_w.double();
449 let inner = if v.ge_mag(two_w) {
450 let inv = one_w.div(v, w);
452 let root = one_w.sub(inv.mul(inv, w)).sqrt(w);
453 crate::log_exp_strict::ln_fixed(v, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
454 } else {
455 let root = v.mul(v, w).sub(one_w).sqrt(w);
457 crate::log_exp_strict::ln_fixed(v.add(root), w)
458 };
459 let raw = inner
460 .round_to_i128(w, SCALE)
461 .expect("D38::acosh: result out of range");
462 Self::from_bits(raw)
463 }
464
465 #[inline]
473 #[must_use]
474 pub fn atanh_strict(self) -> Self {
475 use crate::d_w128_kernels::Fixed;
476 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
477 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
478 let v = to_fixed(self.0);
479 let ax = Fixed { negative: false, mag: v.mag };
480 assert!(!ax.ge_mag(one_w), "D38::atanh: argument out of domain (-1, 1)");
481 let ratio = one_w.add(v).div(one_w.sub(v), w);
483 let raw = crate::log_exp_strict::ln_fixed(ratio, w)
484 .halve()
485 .round_to_i128(w, SCALE)
486 .expect("D38::atanh: result out of range");
487 Self::from_bits(raw)
488 }
489
490 #[inline]
494 #[must_use]
495 pub fn to_degrees_strict(self) -> Self {
496 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
497 let raw = to_fixed(self.0)
498 .mul_u128(180)
499 .div(wide_pi(w), w)
500 .round_to_i128(w, SCALE)
501 .expect("D38::to_degrees: result out of range");
502 Self::from_bits(raw)
503 }
504
505 #[inline]
508 #[must_use]
509 pub fn to_radians_strict(self) -> Self {
510 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
511 let raw = to_fixed(self.0)
512 .mul(wide_pi(w), w)
513 .div_small(180)
514 .round_to_i128(w, SCALE)
515 .expect("D38::to_radians: result out of range");
516 Self::from_bits(raw)
517 }
518}
519
520
521fn wide_pi(w: u32) -> crate::d_w128_kernels::Fixed {
557 crate::d_w128_kernels::Fixed::from_decimal_split(
559 31_415_926_535_897_932_384_626_433_832_795_u128,
560 2_884_197_169_399_375_105_820_974_944_592_u128,
561 )
562 .rescale_down(63, w)
563}
564
565fn wide_tau(w: u32) -> crate::d_w128_kernels::Fixed {
567 wide_pi(w).double()
568}
569
570fn wide_half_pi(w: u32) -> crate::d_w128_kernels::Fixed {
572 wide_pi(w).halve()
573}
574
575fn to_fixed(raw: i128) -> crate::d_w128_kernels::Fixed {
578 use crate::d_w128_kernels::Fixed;
579 let m = Fixed::from_u128_mag(raw.unsigned_abs(), false)
580 .mul_u128(10u128.pow(crate::log_exp_strict::STRICT_GUARD));
581 if raw < 0 {
582 m.neg()
583 } else {
584 m
585 }
586}
587
588fn sin_taylor(r: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
591 let r2 = r.mul(r, w);
592 let mut sum = r;
593 let mut term = r; let mut k: u128 = 1;
595 loop {
596 term = term.mul(r2, w).div_small((2 * k) * (2 * k + 1));
598 if term.is_zero() {
599 break;
600 }
601 if k % 2 == 1 {
602 sum = sum.sub(term);
603 } else {
604 sum = sum.add(term);
605 }
606 k += 1;
607 if k > 200 {
608 break;
609 }
610 }
611 sum
612}
613
614fn sin_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
620 use crate::d_w128_kernels::Fixed;
621 let tau = wide_tau(w);
622 let pi = wide_pi(w);
623 let half_pi = wide_half_pi(w);
624
625 let q = v_w.div(tau, w).round_to_nearest_int(w);
627 let q_tau = if q >= 0 {
628 tau.mul_u128(q as u128)
629 } else {
630 tau.mul_u128((-q) as u128).neg()
631 };
632 let r = v_w.sub(q_tau);
633
634 let sign = r.negative;
636 let abs_r = Fixed { negative: false, mag: r.mag };
637 let reduced = if abs_r.ge_mag(half_pi) {
638 pi.sub(abs_r)
639 } else {
640 abs_r
641 };
642 let s = sin_taylor(reduced, w);
643 if sign {
644 s.neg()
645 } else {
646 s
647 }
648}
649
650fn atan_taylor(x: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
653 let x2 = x.mul(x, w);
654 let mut sum = x;
655 let mut term = x; let mut k: u128 = 1;
657 loop {
658 term = term.mul(x2, w);
659 let contrib = term.div_small(2 * k + 1);
660 if contrib.is_zero() {
661 break;
662 }
663 if k % 2 == 1 {
664 sum = sum.sub(contrib);
665 } else {
666 sum = sum.add(contrib);
667 }
668 k += 1;
669 if k > 300 {
670 break;
671 }
672 }
673 sum
674}
675
676fn atan_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
683 use crate::d_w128_kernels::Fixed;
684 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
685 let sign = v_w.negative;
686 let mut x = Fixed { negative: false, mag: v_w.mag };
687 let mut add_half_pi = false;
688 if x.ge_mag(one_w) && x != one_w {
689 x = one_w.div(x, w); add_half_pi = true;
691 }
692 let halvings: u32 = 3;
694 for _ in 0..halvings {
695 let x2 = x.mul(x, w);
696 let denom = one_w.add(one_w.add(x2).sqrt(w));
697 x = x.div(denom, w);
698 }
699 let mut result = atan_taylor(x, w);
700 result = result.shl(halvings);
701 if add_half_pi {
702 result = wide_half_pi(w).sub(result);
703 }
704 if sign {
705 result.neg()
706 } else {
707 result
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use crate::consts::DecimalConsts;
714 use crate::core_type::D38s12;
715
716 const TWO_LSB: i128 = 2;
722
723 const FOUR_LSB: i128 = 4;
727
728 const ANGLE_TOLERANCE_LSB: i128 = 32;
736
737 fn within_lsb(actual: D38s12, expected: D38s12, lsb: i128) -> bool {
738 let diff = (actual.to_bits() - expected.to_bits()).abs();
739 diff <= lsb
740 }
741
742 #[cfg(all(feature = "strict", not(feature = "fast")))]
751 #[test]
752 fn strict_trig_family_matches_f64() {
753 use crate::core_type::D38;
754 macro_rules! check {
755 ($name:literal, $raw:expr, $strict:expr, $f64expr:expr) => {{
756 let strict: i128 = $strict;
757 let v = $raw as f64 / 1e9;
758 let reference = ($f64expr(v) * 1e9).round() as i128;
759 assert!(
760 (strict - reference).abs() <= 2,
761 concat!($name, "({}) = {}, f64 reference {}"),
762 $raw,
763 strict,
764 reference
765 );
766 }};
767 }
768 for &raw in &[
770 -7_000_000_000_i128, -1_000_000_000, -100_000_000, 1,
771 500_000_000, 1_000_000_000, 1_570_796_327, 3_000_000_000,
772 6_283_185_307, 12_000_000_000,
773 ] {
774 let x = D38::<9>::from_bits(raw);
775 check!("sin", raw, x.sin_strict().to_bits(), f64::sin);
776 check!("cos", raw, x.cos_strict().to_bits(), f64::cos);
777 check!("atan", raw, x.atan_strict().to_bits(), f64::atan);
778 check!("sinh", raw, x.sinh_strict().to_bits(), f64::sinh);
779 check!("cosh", raw, x.cosh_strict().to_bits(), f64::cosh);
780 check!("tanh", raw, x.tanh_strict().to_bits(), f64::tanh);
781 check!("asinh", raw, x.asinh_strict().to_bits(), f64::asinh);
782 }
783 for &raw in &[
785 -1_000_000_000_i128, -700_000_000, -100_000_000, 0,
786 250_000_000, 500_000_000, 999_999_999,
787 ] {
788 let x = D38::<9>::from_bits(raw);
789 check!("asin", raw, x.asin_strict().to_bits(), f64::asin);
790 check!("acos", raw, x.acos_strict().to_bits(), f64::acos);
791 }
792 for &raw in &[-900_000_000_i128, -300_000_000, 1, 300_000_000, 900_000_000] {
794 let x = D38::<9>::from_bits(raw);
795 check!("atanh", raw, x.atanh_strict().to_bits(), f64::atanh);
796 }
797 for &raw in &[1_000_000_000_i128, 1_500_000_000, 3_000_000_000, 50_000_000_000] {
799 let x = D38::<9>::from_bits(raw);
800 check!("acosh", raw, x.acosh_strict().to_bits(), f64::acosh);
801 }
802 for &raw in &[-1_000_000_000_i128, 1, 500_000_000, 1_000_000_000, 1_400_000_000] {
804 let x = D38::<9>::from_bits(raw);
805 check!("tan", raw, x.tan_strict().to_bits(), f64::tan);
806 }
807 }
808
809 #[test]
811 fn sin_zero_is_zero() {
812 assert_eq!(D38s12::ZERO.sin(), D38s12::ZERO);
813 }
814
815 #[test]
817 fn cos_zero_is_one() {
818 assert_eq!(D38s12::ZERO.cos(), D38s12::ONE);
819 }
820
821 #[test]
823 fn tan_zero_is_zero() {
824 assert_eq!(D38s12::ZERO.tan(), D38s12::ZERO);
825 }
826
827 #[test]
831 fn sin_squared_plus_cos_squared_is_one() {
832 for raw in [
833 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, ] {
839 let x = D38s12::from_bits(raw);
840 let s = x.sin();
841 let c = x.cos();
842 let sum = (s * s) + (c * c);
843 assert!(
844 within_lsb(sum, D38s12::ONE, FOUR_LSB),
845 "sin^2 + cos^2 != 1 for raw={raw}: got bits {} (delta {})",
846 sum.to_bits(),
847 (sum.to_bits() - D38s12::ONE.to_bits()).abs(),
848 );
849 }
850 }
851
852 #[test]
856 fn asin_zero_is_zero() {
857 assert_eq!(D38s12::ZERO.asin(), D38s12::ZERO);
858 }
859
860 #[test]
862 fn acos_one_is_zero() {
863 assert_eq!(D38s12::ONE.acos(), D38s12::ZERO);
864 }
865
866 #[test]
868 fn acos_zero_is_half_pi() {
869 let result = D38s12::ZERO.acos();
870 assert!(
871 within_lsb(result, D38s12::half_pi(), FOUR_LSB),
872 "acos(0) bits {}, half_pi bits {}",
873 result.to_bits(),
874 D38s12::half_pi().to_bits(),
875 );
876 }
877
878 #[test]
880 fn atan_zero_is_zero() {
881 assert_eq!(D38s12::ZERO.atan(), D38s12::ZERO);
882 }
883
884 #[test]
887 fn asin_of_sin_round_trip() {
888 for raw in [
889 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, ] {
896 let x = D38s12::from_bits(raw);
897 let recovered = x.sin().asin();
898 assert!(
899 within_lsb(recovered, x, FOUR_LSB),
900 "asin(sin(x)) != x for raw={raw}: got bits {} (delta {})",
901 recovered.to_bits(),
902 (recovered.to_bits() - x.to_bits()).abs(),
903 );
904 }
905 }
906
907 #[test]
911 fn atan2_first_quadrant_diagonal() {
912 let one = D38s12::ONE;
913 let result = one.atan2(one);
914 assert!(
915 within_lsb(result, D38s12::quarter_pi(), TWO_LSB),
916 "atan2(1, 1) bits {}, quarter_pi bits {}",
917 result.to_bits(),
918 D38s12::quarter_pi().to_bits(),
919 );
920 }
921
922 #[test]
924 fn atan2_third_quadrant_diagonal() {
925 let neg_one = -D38s12::ONE;
926 let result = neg_one.atan2(neg_one);
927 let three = D38s12::from_int(3);
928 let expected = -(D38s12::quarter_pi() * three);
929 assert!(
930 within_lsb(result, expected, TWO_LSB),
931 "atan2(-1, -1) bits {}, expected -3pi/4 bits {}",
932 result.to_bits(),
933 expected.to_bits(),
934 );
935 }
936
937 #[test]
939 fn atan2_second_quadrant_diagonal() {
940 let one = D38s12::ONE;
941 let neg_one = -D38s12::ONE;
942 let result = one.atan2(neg_one);
943 let three = D38s12::from_int(3);
944 let expected = D38s12::quarter_pi() * three;
945 assert!(
946 within_lsb(result, expected, TWO_LSB),
947 "atan2(1, -1) bits {}, expected 3pi/4 bits {}",
948 result.to_bits(),
949 expected.to_bits(),
950 );
951 }
952
953 #[test]
955 fn atan2_fourth_quadrant_diagonal() {
956 let one = D38s12::ONE;
957 let neg_one = -D38s12::ONE;
958 let result = neg_one.atan2(one);
959 let expected = -D38s12::quarter_pi();
960 assert!(
961 within_lsb(result, expected, TWO_LSB),
962 "atan2(-1, 1) bits {}, expected -pi/4 bits {}",
963 result.to_bits(),
964 expected.to_bits(),
965 );
966 }
967
968 #[test]
970 fn atan2_positive_x_axis_is_zero() {
971 let zero = D38s12::ZERO;
972 let one = D38s12::ONE;
973 assert_eq!(zero.atan2(one), D38s12::ZERO);
974 }
975
976 #[test]
980 fn sinh_zero_is_zero() {
981 assert_eq!(D38s12::ZERO.sinh(), D38s12::ZERO);
982 }
983
984 #[test]
986 fn cosh_zero_is_one() {
987 assert_eq!(D38s12::ZERO.cosh(), D38s12::ONE);
988 }
989
990 #[test]
992 fn tanh_zero_is_zero() {
993 assert_eq!(D38s12::ZERO.tanh(), D38s12::ZERO);
994 }
995
996 #[test]
998 fn asinh_zero_is_zero() {
999 assert_eq!(D38s12::ZERO.asinh(), D38s12::ZERO);
1000 }
1001
1002 #[test]
1004 fn acosh_one_is_zero() {
1005 assert_eq!(D38s12::ONE.acosh(), D38s12::ZERO);
1006 }
1007
1008 #[test]
1010 fn atanh_zero_is_zero() {
1011 assert_eq!(D38s12::ZERO.atanh(), D38s12::ZERO);
1012 }
1013
1014 #[test]
1017 fn cosh_squared_minus_sinh_squared_is_one() {
1018 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1019 for raw in [
1020 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, ] {
1026 let x = D38s12::from_bits(raw);
1027 let ch = x.cosh();
1028 let sh = x.sinh();
1029 let diff = (ch * ch) - (sh * sh);
1030 assert!(
1031 within_lsb(diff, D38s12::ONE, FOUR_LSB),
1032 "cosh^2 - sinh^2 != 1 for raw={raw}: got bits {} (delta {})",
1033 diff.to_bits(),
1034 (diff.to_bits() - D38s12::ONE.to_bits()).abs(),
1035 );
1036 }
1037 }
1038
1039 #[test]
1045 fn to_degrees_pi_is_180() {
1046 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1047 let pi = D38s12::pi();
1048 let result = pi.to_degrees();
1049 let expected = D38s12::from_int(180);
1050 assert!(
1051 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1052 "to_degrees(pi) bits {}, expected 180 bits {} (delta {})",
1053 result.to_bits(),
1054 expected.to_bits(),
1055 (result.to_bits() - expected.to_bits()).abs(),
1056 );
1057 }
1058
1059 #[test]
1061 fn to_radians_180_is_pi() {
1062 let one_eighty = D38s12::from_int(180);
1063 let result = one_eighty.to_radians();
1064 let expected = D38s12::pi();
1065 assert!(
1066 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1067 "to_radians(180) bits {}, expected pi bits {} (delta {})",
1068 result.to_bits(),
1069 expected.to_bits(),
1070 (result.to_bits() - expected.to_bits()).abs(),
1071 );
1072 }
1073
1074 #[test]
1076 fn to_degrees_zero_is_zero() {
1077 assert_eq!(D38s12::ZERO.to_degrees(), D38s12::ZERO);
1078 }
1079
1080 #[test]
1082 fn to_radians_zero_is_zero() {
1083 assert_eq!(D38s12::ZERO.to_radians(), D38s12::ZERO);
1084 }
1085
1086 #[test]
1089 fn to_radians_to_degrees_round_trip() {
1090 for raw in [
1091 500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
1096 let x = D38s12::from_bits(raw);
1097 let recovered = x.to_degrees().to_radians();
1098 assert!(
1099 within_lsb(recovered, x, FOUR_LSB),
1100 "to_radians(to_degrees(x)) != x for raw={raw}: got bits {} (delta {})",
1101 recovered.to_bits(),
1102 (recovered.to_bits() - x.to_bits()).abs(),
1103 );
1104 }
1105 }
1106
1107 #[test]
1109 fn to_degrees_half_pi_is_90() {
1110 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1111 let result = D38s12::half_pi().to_degrees();
1112 let expected = D38s12::from_int(90);
1113 assert!(
1114 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1115 "to_degrees(half_pi) bits {}, expected 90 bits {} (delta {})",
1116 result.to_bits(),
1117 expected.to_bits(),
1118 (result.to_bits() - expected.to_bits()).abs(),
1119 );
1120 }
1121
1122 #[test]
1124 fn to_degrees_quarter_pi_is_45() {
1125 let result = D38s12::quarter_pi().to_degrees();
1126 let expected = D38s12::from_int(45);
1127 assert!(
1128 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1129 "to_degrees(quarter_pi) bits {}, expected 45 bits {} (delta {})",
1130 result.to_bits(),
1131 expected.to_bits(),
1132 (result.to_bits() - expected.to_bits()).abs(),
1133 );
1134 }
1135
1136 #[test]
1141 fn tan_matches_sin_over_cos() {
1142 for raw in [
1143 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, ] {
1149 let x = D38s12::from_bits(raw);
1150 let t = x.tan();
1151 let sc = x.sin() / x.cos();
1152 assert!(
1153 within_lsb(t, sc, FOUR_LSB),
1154 "tan(x) != sin/cos for raw={raw}: tan bits {}, sin/cos bits {}",
1155 t.to_bits(),
1156 sc.to_bits(),
1157 );
1158 }
1159 }
1160
1161 #[test]
1164 fn tanh_matches_sinh_over_cosh() {
1165 for raw in [
1166 500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
1171 let x = D38s12::from_bits(raw);
1172 let t = x.tanh();
1173 let sc = x.sinh() / x.cosh();
1174 assert!(
1175 within_lsb(t, sc, FOUR_LSB),
1176 "tanh(x) != sinh/cosh for raw={raw}: tanh bits {}, sinh/cosh bits {}",
1177 t.to_bits(),
1178 sc.to_bits(),
1179 );
1180 }
1181 }
1182}
1183