1use crate::core_type::D38;
63
64impl<const SCALE: u32> D38<SCALE> {
65#[cfg(all(feature = "strict", not(feature = "fast")))]
66 #[inline]
69 #[must_use]
70 pub fn sin(self) -> Self {
71 self.sin_strict()
72 }
73#[cfg(all(feature = "strict", not(feature = "fast")))]
74
75 #[inline]
78 #[must_use]
79 pub fn cos(self) -> Self {
80 self.cos_strict()
81 }
82#[cfg(all(feature = "strict", not(feature = "fast")))]
83
84 #[inline]
87 #[must_use]
88 pub fn tan(self) -> Self {
89 self.tan_strict()
90 }
91#[cfg(all(feature = "strict", not(feature = "fast")))]
92
93 #[inline]
96 #[must_use]
97 pub fn asin(self) -> Self {
98 self.asin_strict()
99 }
100#[cfg(all(feature = "strict", not(feature = "fast")))]
101
102 #[inline]
105 #[must_use]
106 pub fn acos(self) -> Self {
107 self.acos_strict()
108 }
109#[cfg(all(feature = "strict", not(feature = "fast")))]
110
111 #[inline]
114 #[must_use]
115 pub fn atan(self) -> Self {
116 self.atan_strict()
117 }
118#[cfg(all(feature = "strict", not(feature = "fast")))]
119
120 #[inline]
124 #[must_use]
125 pub fn atan2(self, other: Self) -> Self {
126 self.atan2_strict(other)
127 }
128#[cfg(all(feature = "strict", not(feature = "fast")))]
129
130 #[inline]
133 #[must_use]
134 pub fn sinh(self) -> Self {
135 self.sinh_strict()
136 }
137#[cfg(all(feature = "strict", not(feature = "fast")))]
138
139 #[inline]
142 #[must_use]
143 pub fn cosh(self) -> Self {
144 self.cosh_strict()
145 }
146#[cfg(all(feature = "strict", not(feature = "fast")))]
147
148 #[inline]
151 #[must_use]
152 pub fn tanh(self) -> Self {
153 self.tanh_strict()
154 }
155#[cfg(all(feature = "strict", not(feature = "fast")))]
156
157 #[inline]
160 #[must_use]
161 pub fn asinh(self) -> Self {
162 self.asinh_strict()
163 }
164#[cfg(all(feature = "strict", not(feature = "fast")))]
165
166 #[inline]
169 #[must_use]
170 pub fn acosh(self) -> Self {
171 self.acosh_strict()
172 }
173#[cfg(all(feature = "strict", not(feature = "fast")))]
174
175 #[inline]
178 #[must_use]
179 pub fn atanh(self) -> Self {
180 self.atanh_strict()
181 }
182#[cfg(all(feature = "strict", not(feature = "fast")))]
183
184 #[inline]
187 #[must_use]
188 pub fn to_degrees(self) -> Self {
189 self.to_degrees_strict()
190 }
191#[cfg(all(feature = "strict", not(feature = "fast")))]
192
193 #[inline]
196 #[must_use]
197 pub fn to_radians(self) -> Self {
198 self.to_radians_strict()
199 }
200
201 #[inline]
204 #[must_use]
205 pub fn sin_strict(self) -> Self {
206 self.sin_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
207 }
208
209 #[inline]
211 #[must_use]
212 pub fn sin_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
213 if self.0 == 0 {
214 return Self::ZERO;
215 }
216 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
217 return self;
218 }
219 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
220 let raw = sin_fixed(to_fixed(self.0), w)
221 .round_to_i128_with(w, SCALE, mode)
222 .expect("D38::sin: result out of range");
223 Self::from_bits(raw)
224 }
225
226 #[inline]
228 #[must_use]
229 pub fn sin_approx(self, working_digits: u32) -> Self {
230 self.sin_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
231 }
232
233 #[inline]
235 #[must_use]
236 pub fn sin_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
237 if working_digits == crate::log_exp_strict::STRICT_GUARD {
238 return self.sin_strict_with(mode);
239 }
240 if self.0 == 0 {
241 return Self::ZERO;
242 }
243 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
244 return self;
245 }
246 let w = SCALE + working_digits;
247 let raw = sin_fixed(to_fixed(self.0), w)
248 .round_to_i128_with(w, SCALE, mode)
249 .expect("D38::sin: result out of range");
250 Self::from_bits(raw)
251 }
252
253 #[inline]
256 #[must_use]
257 pub fn cos_strict(self) -> Self {
258 self.cos_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
259 }
260
261 #[inline]
263 #[must_use]
264 pub fn cos_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
265 if self.0 == 0 {
266 return Self::from_bits(10_i128.pow(SCALE));
267 }
268 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
269 let arg = to_fixed(self.0).add(wide_half_pi(w));
270 let raw = sin_fixed(arg, w)
271 .round_to_i128_with(w, SCALE, mode)
272 .expect("D38::cos: result out of range");
273 Self::from_bits(raw)
274 }
275
276 #[inline]
278 #[must_use]
279 pub fn cos_approx(self, working_digits: u32) -> Self {
280 self.cos_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
281 }
282
283 #[inline]
285 #[must_use]
286 pub fn cos_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
287 if working_digits == crate::log_exp_strict::STRICT_GUARD {
288 return self.cos_strict_with(mode);
289 }
290 if self.0 == 0 {
291 return Self::from_bits(10_i128.pow(SCALE));
292 }
293 let w = SCALE + working_digits;
294 let arg = to_fixed(self.0).add(wide_half_pi(w));
295 let raw = sin_fixed(arg, w)
296 .round_to_i128_with(w, SCALE, mode)
297 .expect("D38::cos: result out of range");
298 Self::from_bits(raw)
299 }
300
301 #[inline]
309 #[must_use]
310 pub fn tan_strict(self) -> Self {
311 self.tan_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
312 }
313
314 #[inline]
316 #[must_use]
317 pub fn tan_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
318 if self.0 == 0 {
319 return Self::ZERO;
320 }
321 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
322 return self;
323 }
324 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
325 let v = to_fixed(self.0);
326 let sin_w = sin_fixed(v, w);
327 let cos_w = sin_fixed(v.add(wide_half_pi(w)), w);
328 assert!(!cos_w.is_zero(), "D38::tan: cosine is zero (argument is an odd multiple of pi/2)");
329 let raw = sin_w
330 .div(cos_w, w)
331 .round_to_i128_with(w, SCALE, mode)
332 .expect("D38::tan: result out of range");
333 Self::from_bits(raw)
334 }
335
336 #[inline]
338 #[must_use]
339 pub fn tan_approx(self, working_digits: u32) -> Self {
340 self.tan_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
341 }
342
343 #[inline]
345 #[must_use]
346 pub fn tan_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
347 if working_digits == crate::log_exp_strict::STRICT_GUARD {
348 return self.tan_strict_with(mode);
349 }
350 if self.0 == 0 {
351 return Self::ZERO;
352 }
353 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
354 return self;
355 }
356 let w = SCALE + working_digits;
357 let v = to_fixed(self.0);
358 let sin_w = sin_fixed(v, w);
359 let cos_w = sin_fixed(v.add(wide_half_pi(w)), w);
360 assert!(!cos_w.is_zero(), "D38::tan: cosine is zero (argument is an odd multiple of pi/2)");
361 let raw = sin_w
362 .div(cos_w, w)
363 .round_to_i128_with(w, SCALE, mode)
364 .expect("D38::tan: result out of range");
365 Self::from_bits(raw)
366 }
367
368 #[inline]
371 #[must_use]
372 pub fn atan_strict(self) -> Self {
373 self.atan_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
374 }
375
376 #[inline]
378 #[must_use]
379 pub fn atan_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
380 use crate::consts::DecimalConsts;
381 if self.0 == 0 {
382 return Self::ZERO;
383 }
384 let one_bits: i128 = 10_i128.pow(SCALE);
385 if self.0 == one_bits {
386 return Self::quarter_pi();
387 }
388 if self.0 == -one_bits {
389 return -Self::quarter_pi();
390 }
391 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
392 return self;
393 }
394 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
395 let raw = atan_fixed(to_fixed(self.0), w)
396 .round_to_i128_with(w, SCALE, mode)
397 .expect("D38::atan: result out of range");
398 Self::from_bits(raw)
399 }
400
401 #[inline]
403 #[must_use]
404 pub fn atan_approx(self, working_digits: u32) -> Self {
405 self.atan_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
406 }
407
408 #[inline]
410 #[must_use]
411 pub fn atan_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
412 if working_digits == crate::log_exp_strict::STRICT_GUARD {
413 return self.atan_strict_with(mode);
414 }
415 use crate::consts::DecimalConsts;
416 if self.0 == 0 {
417 return Self::ZERO;
418 }
419 let one_bits: i128 = 10_i128.pow(SCALE);
420 if self.0 == one_bits {
421 return Self::quarter_pi();
422 }
423 if self.0 == -one_bits {
424 return -Self::quarter_pi();
425 }
426 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
427 return self;
428 }
429 let w = SCALE + working_digits;
430 let raw = atan_fixed(to_fixed(self.0), w)
431 .round_to_i128_with(w, SCALE, mode)
432 .expect("D38::atan: result out of range");
433 Self::from_bits(raw)
434 }
435
436 #[inline]
445 #[must_use]
446 pub fn asin_strict(self) -> Self {
447 self.asin_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
448 }
449
450 #[inline]
452 #[must_use]
453 pub fn asin_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
454 if self.0 == 0 {
455 return Self::ZERO;
456 }
457 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
458 return self;
459 }
460 use crate::d_w128_kernels::Fixed;
461 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
462 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
463 let v = to_fixed(self.0);
464 let abs_v = Fixed { negative: false, mag: v.mag };
465 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::asin: argument out of domain [-1, 1]");
466 if abs_v == one_w {
467 let hp = wide_half_pi(w);
468 let hp = if v.negative { hp.neg() } else { hp };
469 let raw = hp
470 .round_to_i128_with(w, SCALE, mode)
471 .expect("D38::asin: result out of range");
472 return Self::from_bits(raw);
473 }
474 let half_w = one_w.halve();
475 let asin_w = if !abs_v.ge_mag(half_w) {
476 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
477 atan_fixed(v.div(denom, w), w)
478 } else {
479 let inner = one_w.sub(abs_v).halve();
480 let inner_sqrt = inner.sqrt(w);
481 let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
482 let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
483 let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
484 if v.negative { result_abs.neg() } else { result_abs }
485 };
486 let raw = asin_w
487 .round_to_i128_with(w, SCALE, mode)
488 .expect("D38::asin: result out of range");
489 Self::from_bits(raw)
490 }
491
492 #[inline]
494 #[must_use]
495 pub fn asin_approx(self, working_digits: u32) -> Self {
496 self.asin_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
497 }
498
499 #[inline]
501 #[must_use]
502 pub fn asin_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
503 if working_digits == crate::log_exp_strict::STRICT_GUARD {
504 return self.asin_strict_with(mode);
505 }
506 if self.0 == 0 {
507 return Self::ZERO;
508 }
509 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
510 return self;
511 }
512 use crate::d_w128_kernels::Fixed;
513 let w = SCALE + working_digits;
514 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
515 let v = to_fixed_w(self.0, working_digits);
516 let abs_v = Fixed { negative: false, mag: v.mag };
517 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::asin: argument out of domain [-1, 1]");
518 if abs_v == one_w {
519 let hp = wide_half_pi(w);
520 let hp = if v.negative { hp.neg() } else { hp };
521 let raw = hp
522 .round_to_i128_with(w, SCALE, mode)
523 .expect("D38::asin: result out of range");
524 return Self::from_bits(raw);
525 }
526 let half_w = one_w.halve();
527 let asin_w = if !abs_v.ge_mag(half_w) {
528 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
529 atan_fixed(v.div(denom, w), w)
530 } else {
531 let inner = one_w.sub(abs_v).halve();
532 let inner_sqrt = inner.sqrt(w);
533 let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
534 let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
535 let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
536 if v.negative { result_abs.neg() } else { result_abs }
537 };
538 let raw = asin_w
539 .round_to_i128_with(w, SCALE, mode)
540 .expect("D38::asin: result out of range");
541 Self::from_bits(raw)
542 }
543
544 #[inline]
551 #[must_use]
552 pub fn acos_strict(self) -> Self {
553 self.acos_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
554 }
555
556 #[inline]
558 #[must_use]
559 pub fn acos_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
560 use crate::consts::DecimalConsts;
561 if self.0 == 0 {
562 return Self::half_pi();
563 }
564 let one_bits: i128 = 10_i128.pow(SCALE);
565 if self.0 == one_bits {
566 return Self::ZERO;
567 }
568 if self.0 == -one_bits {
569 return Self::pi();
570 }
571 use crate::d_w128_kernels::Fixed;
572 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
573 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
574 let v = to_fixed(self.0);
575 let abs_v = Fixed { negative: false, mag: v.mag };
576 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::acos: argument out of domain [-1, 1]");
577 let half_w = one_w.halve();
578 let asin_w = if abs_v == one_w {
579 let hp = wide_half_pi(w);
580 if v.negative { hp.neg() } else { hp }
581 } else if !abs_v.ge_mag(half_w) {
582 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
583 atan_fixed(v.div(denom, w), w)
584 } else {
585 let inner = one_w.sub(abs_v).halve();
586 let inner_sqrt = inner.sqrt(w);
587 let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
588 let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
589 let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
590 if v.negative { result_abs.neg() } else { result_abs }
591 };
592 let raw = wide_half_pi(w)
593 .sub(asin_w)
594 .round_to_i128_with(w, SCALE, mode)
595 .expect("D38::acos: result out of range");
596 Self::from_bits(raw)
597 }
598
599 #[inline]
601 #[must_use]
602 pub fn acos_approx(self, working_digits: u32) -> Self {
603 self.acos_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
604 }
605
606 #[inline]
608 #[must_use]
609 pub fn acos_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
610 if working_digits == crate::log_exp_strict::STRICT_GUARD {
611 return self.acos_strict_with(mode);
612 }
613 use crate::consts::DecimalConsts;
614 if self.0 == 0 {
615 return Self::half_pi();
616 }
617 let one_bits: i128 = 10_i128.pow(SCALE);
618 if self.0 == one_bits {
619 return Self::ZERO;
620 }
621 if self.0 == -one_bits {
622 return Self::pi();
623 }
624 use crate::d_w128_kernels::Fixed;
625 let w = SCALE + working_digits;
626 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
627 let v = to_fixed_w(self.0, working_digits);
628 let abs_v = Fixed { negative: false, mag: v.mag };
629 assert!(!(abs_v.ge_mag(one_w) && abs_v != one_w), "D38::acos: argument out of domain [-1, 1]");
630 let half_w = one_w.halve();
631 let asin_w = if abs_v == one_w {
632 let hp = wide_half_pi(w);
633 if v.negative { hp.neg() } else { hp }
634 } else if !abs_v.ge_mag(half_w) {
635 let denom = one_w.sub(v.mul(v, w)).sqrt(w);
636 atan_fixed(v.div(denom, w), w)
637 } else {
638 let inner = one_w.sub(abs_v).halve();
639 let inner_sqrt = inner.sqrt(w);
640 let inner_denom = one_w.sub(inner_sqrt.mul(inner_sqrt, w)).sqrt(w);
641 let inner_asin = atan_fixed(inner_sqrt.div(inner_denom, w), w);
642 let result_abs = wide_half_pi(w).sub(inner_asin).sub(inner_asin);
643 if v.negative { result_abs.neg() } else { result_abs }
644 };
645 let raw = wide_half_pi(w)
646 .sub(asin_w)
647 .round_to_i128_with(w, SCALE, mode)
648 .expect("D38::acos: result out of range");
649 Self::from_bits(raw)
650 }
651
652 #[inline]
656 #[must_use]
657 pub fn atan2_strict(self, other: Self) -> Self {
658 self.atan2_strict_with(other, crate::rounding::DEFAULT_ROUNDING_MODE)
659 }
660
661 #[inline]
663 #[must_use]
664 pub fn atan2_strict_with(self, other: Self, mode: crate::rounding::RoundingMode) -> Self {
665 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
666 let raw = atan2_kernel(to_fixed(self.0), to_fixed(other.0), self.0, w)
667 .round_to_i128_with(w, SCALE, mode)
668 .expect("D38::atan2: result out of range");
669 Self::from_bits(raw)
670 }
671
672 #[inline]
674 #[must_use]
675 pub fn atan2_approx(self, other: Self, working_digits: u32) -> Self {
676 self.atan2_approx_with(other, working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
677 }
678
679 #[inline]
681 #[must_use]
682 pub fn atan2_approx_with(self, other: Self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
683 if working_digits == crate::log_exp_strict::STRICT_GUARD {
684 return self.atan2_strict_with(other, mode);
685 }
686 let w = SCALE + working_digits;
687 let raw = atan2_kernel(
688 to_fixed_w(self.0, working_digits),
689 to_fixed_w(other.0, working_digits),
690 self.0,
691 w,
692 )
693 .round_to_i128_with(w, SCALE, mode)
694 .expect("D38::atan2: result out of range");
695 Self::from_bits(raw)
696 }
697
698 #[inline]
702 #[must_use]
703 pub fn sinh_strict(self) -> Self {
704 self.sinh_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
705 }
706
707 #[inline]
709 #[must_use]
710 pub fn sinh_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
711 if self.0 == 0 {
712 return Self::ZERO;
713 }
714 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
715 return self;
716 }
717 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
718 let v = to_fixed(self.0);
719 let ex = crate::log_exp_strict::exp_fixed(v, w);
720 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
721 let raw = ex
722 .sub(enx)
723 .halve()
724 .round_to_i128_with(w, SCALE, mode)
725 .expect("D38::sinh: result out of range");
726 Self::from_bits(raw)
727 }
728
729 #[inline]
731 #[must_use]
732 pub fn sinh_approx(self, working_digits: u32) -> Self {
733 self.sinh_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
734 }
735
736 #[inline]
738 #[must_use]
739 pub fn sinh_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
740 if working_digits == crate::log_exp_strict::STRICT_GUARD {
741 return self.sinh_strict_with(mode);
742 }
743 if self.0 == 0 {
744 return Self::ZERO;
745 }
746 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
747 return self;
748 }
749 let w = SCALE + working_digits;
750 let v = to_fixed_w(self.0, working_digits);
751 let ex = crate::log_exp_strict::exp_fixed(v, w);
752 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
753 let raw = ex
754 .sub(enx)
755 .halve()
756 .round_to_i128_with(w, SCALE, mode)
757 .expect("D38::sinh: result out of range");
758 Self::from_bits(raw)
759 }
760
761 #[inline]
764 #[must_use]
765 pub fn cosh_strict(self) -> Self {
766 self.cosh_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
767 }
768
769 #[inline]
771 #[must_use]
772 pub fn cosh_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
773 if self.0 == 0 {
774 return Self::from_bits(10_i128.pow(SCALE));
775 }
776 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
777 let v = to_fixed(self.0);
778 let ex = crate::log_exp_strict::exp_fixed(v, w);
779 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
780 let raw = ex
781 .add(enx)
782 .halve()
783 .round_to_i128_with(w, SCALE, mode)
784 .expect("D38::cosh: result out of range");
785 Self::from_bits(raw)
786 }
787
788 #[inline]
790 #[must_use]
791 pub fn cosh_approx(self, working_digits: u32) -> Self {
792 self.cosh_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
793 }
794
795 #[inline]
797 #[must_use]
798 pub fn cosh_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
799 if working_digits == crate::log_exp_strict::STRICT_GUARD {
800 return self.cosh_strict_with(mode);
801 }
802 if self.0 == 0 {
803 return Self::from_bits(10_i128.pow(SCALE));
804 }
805 let w = SCALE + working_digits;
806 let v = to_fixed_w(self.0, working_digits);
807 let ex = crate::log_exp_strict::exp_fixed(v, w);
808 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
809 let raw = ex
810 .add(enx)
811 .halve()
812 .round_to_i128_with(w, SCALE, mode)
813 .expect("D38::cosh: result out of range");
814 Self::from_bits(raw)
815 }
816
817 #[inline]
821 #[must_use]
822 pub fn tanh_strict(self) -> Self {
823 self.tanh_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
824 }
825
826 #[inline]
828 #[must_use]
829 pub fn tanh_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
830 if self.0 == 0 {
831 return Self::ZERO;
832 }
833 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
834 return self;
835 }
836 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
837 let v = to_fixed(self.0);
838 let ex = crate::log_exp_strict::exp_fixed(v, w);
839 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
840 let raw = ex
841 .sub(enx)
842 .div(ex.add(enx), w)
843 .round_to_i128_with(w, SCALE, mode)
844 .expect("D38::tanh: result out of range");
845 Self::from_bits(raw)
846 }
847
848 #[inline]
850 #[must_use]
851 pub fn tanh_approx(self, working_digits: u32) -> Self {
852 self.tanh_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
853 }
854
855 #[inline]
857 #[must_use]
858 pub fn tanh_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
859 if working_digits == crate::log_exp_strict::STRICT_GUARD {
860 return self.tanh_strict_with(mode);
861 }
862 if self.0 == 0 {
863 return Self::ZERO;
864 }
865 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
866 return self;
867 }
868 let w = SCALE + working_digits;
869 let v = to_fixed_w(self.0, working_digits);
870 let ex = crate::log_exp_strict::exp_fixed(v, w);
871 let enx = crate::log_exp_strict::exp_fixed(v.neg(), w);
872 let raw = ex
873 .sub(enx)
874 .div(ex.add(enx), w)
875 .round_to_i128_with(w, SCALE, mode)
876 .expect("D38::tanh: result out of range");
877 Self::from_bits(raw)
878 }
879
880 #[inline]
886 #[must_use]
887 pub fn asinh_strict(self) -> Self {
888 self.asinh_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
889 }
890
891 #[inline]
893 #[must_use]
894 pub fn asinh_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
895 use crate::d_w128_kernels::Fixed;
896 if self.0 == 0 {
897 return Self::ZERO;
898 }
899 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
900 return self;
901 }
902 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
903 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
904 let v = to_fixed(self.0);
905 let ax = Fixed { negative: false, mag: v.mag };
906 let inner = if ax.ge_mag(one_w) {
907 let inv = one_w.div(ax, w);
908 let root = one_w.add(inv.mul(inv, w)).sqrt(w);
909 crate::log_exp_strict::ln_fixed(ax, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
910 } else {
911 let root = ax.mul(ax, w).add(one_w).sqrt(w);
912 crate::log_exp_strict::ln_fixed(ax.add(root), w)
913 };
914 let signed = if self.0 < 0 { inner.neg() } else { inner };
915 let raw = signed
916 .round_to_i128_with(w, SCALE, mode)
917 .expect("D38::asinh: result out of range");
918 Self::from_bits(raw)
919 }
920
921 #[inline]
923 #[must_use]
924 pub fn asinh_approx(self, working_digits: u32) -> Self {
925 self.asinh_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
926 }
927
928 #[inline]
930 #[must_use]
931 pub fn asinh_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
932 if working_digits == crate::log_exp_strict::STRICT_GUARD {
933 return self.asinh_strict_with(mode);
934 }
935 use crate::d_w128_kernels::Fixed;
936 if self.0 == 0 {
937 return Self::ZERO;
938 }
939 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
940 return self;
941 }
942 let w = SCALE + working_digits;
943 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
944 let v = to_fixed_w(self.0, working_digits);
945 let ax = Fixed { negative: false, mag: v.mag };
946 let inner = if ax.ge_mag(one_w) {
947 let inv = one_w.div(ax, w);
948 let root = one_w.add(inv.mul(inv, w)).sqrt(w);
949 crate::log_exp_strict::ln_fixed(ax, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
950 } else {
951 let root = ax.mul(ax, w).add(one_w).sqrt(w);
952 crate::log_exp_strict::ln_fixed(ax.add(root), w)
953 };
954 let signed = if self.0 < 0 { inner.neg() } else { inner };
955 let raw = signed
956 .round_to_i128_with(w, SCALE, mode)
957 .expect("D38::asinh: result out of range");
958 Self::from_bits(raw)
959 }
960
961 #[inline]
970 #[must_use]
971 pub fn acosh_strict(self) -> Self {
972 self.acosh_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
973 }
974
975 #[inline]
977 #[must_use]
978 pub fn acosh_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
979 let one_bits: i128 = 10_i128.pow(SCALE);
980 if self.0 == one_bits {
981 return Self::ZERO;
982 }
983 use crate::d_w128_kernels::Fixed;
984 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
985 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
986 let v = to_fixed(self.0);
987 assert!(!v.negative && v.ge_mag(one_w), "D38::acosh: argument must be >= 1");
988 let two_w = one_w.double();
989 let inner = if v.ge_mag(two_w) {
990 let inv = one_w.div(v, w);
991 let root = one_w.sub(inv.mul(inv, w)).sqrt(w);
992 crate::log_exp_strict::ln_fixed(v, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
993 } else {
994 let root = v.mul(v, w).sub(one_w).sqrt(w);
995 crate::log_exp_strict::ln_fixed(v.add(root), w)
996 };
997 let raw = inner
998 .round_to_i128_with(w, SCALE, mode)
999 .expect("D38::acosh: result out of range");
1000 Self::from_bits(raw)
1001 }
1002
1003 #[inline]
1005 #[must_use]
1006 pub fn acosh_approx(self, working_digits: u32) -> Self {
1007 self.acosh_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
1008 }
1009
1010 #[inline]
1012 #[must_use]
1013 pub fn acosh_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
1014 if working_digits == crate::log_exp_strict::STRICT_GUARD {
1015 return self.acosh_strict_with(mode);
1016 }
1017 let one_bits: i128 = 10_i128.pow(SCALE);
1018 if self.0 == one_bits {
1019 return Self::ZERO;
1020 }
1021 use crate::d_w128_kernels::Fixed;
1022 let w = SCALE + working_digits;
1023 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
1024 let v = to_fixed_w(self.0, working_digits);
1025 assert!(!v.negative && v.ge_mag(one_w), "D38::acosh: argument must be >= 1");
1026 let two_w = one_w.double();
1027 let inner = if v.ge_mag(two_w) {
1028 let inv = one_w.div(v, w);
1029 let root = one_w.sub(inv.mul(inv, w)).sqrt(w);
1030 crate::log_exp_strict::ln_fixed(v, w).add(crate::log_exp_strict::ln_fixed(one_w.add(root), w))
1031 } else {
1032 let root = v.mul(v, w).sub(one_w).sqrt(w);
1033 crate::log_exp_strict::ln_fixed(v.add(root), w)
1034 };
1035 let raw = inner
1036 .round_to_i128_with(w, SCALE, mode)
1037 .expect("D38::acosh: result out of range");
1038 Self::from_bits(raw)
1039 }
1040
1041 #[inline]
1049 #[must_use]
1050 pub fn atanh_strict(self) -> Self {
1051 self.atanh_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
1052 }
1053
1054 #[inline]
1056 #[must_use]
1057 pub fn atanh_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
1058 if self.0 == 0 {
1059 return Self::ZERO;
1060 }
1061 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
1062 return self;
1063 }
1064 use crate::d_w128_kernels::Fixed;
1065 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
1066 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
1067 let v = to_fixed(self.0);
1068 let ax = Fixed { negative: false, mag: v.mag };
1069 assert!(!ax.ge_mag(one_w), "D38::atanh: argument out of domain (-1, 1)");
1070 let ratio = one_w.add(v).div(one_w.sub(v), w);
1071 let raw = crate::log_exp_strict::ln_fixed(ratio, w)
1072 .halve()
1073 .round_to_i128_with(w, SCALE, mode)
1074 .expect("D38::atanh: result out of range");
1075 Self::from_bits(raw)
1076 }
1077
1078 #[inline]
1080 #[must_use]
1081 pub fn atanh_approx(self, working_digits: u32) -> Self {
1082 self.atanh_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
1083 }
1084
1085 #[inline]
1087 #[must_use]
1088 pub fn atanh_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
1089 if working_digits == crate::log_exp_strict::STRICT_GUARD {
1090 return self.atanh_strict_with(mode);
1091 }
1092 if self.0 == 0 {
1093 return Self::ZERO;
1094 }
1095 if self.0.abs() <= small_x_linear_threshold::<SCALE>() {
1096 return self;
1097 }
1098 use crate::d_w128_kernels::Fixed;
1099 let w = SCALE + working_digits;
1100 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
1101 let v = to_fixed_w(self.0, working_digits);
1102 let ax = Fixed { negative: false, mag: v.mag };
1103 assert!(!ax.ge_mag(one_w), "D38::atanh: argument out of domain (-1, 1)");
1104 let ratio = one_w.add(v).div(one_w.sub(v), w);
1105 let raw = crate::log_exp_strict::ln_fixed(ratio, w)
1106 .halve()
1107 .round_to_i128_with(w, SCALE, mode)
1108 .expect("D38::atanh: result out of range");
1109 Self::from_bits(raw)
1110 }
1111
1112 #[inline]
1116 #[must_use]
1117 pub fn to_degrees_strict(self) -> Self {
1118 self.to_degrees_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
1119 }
1120
1121 #[inline]
1123 #[must_use]
1124 pub fn to_degrees_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
1125 if self.0 == 0 {
1126 return Self::ZERO;
1127 }
1128 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
1129 let raw = to_fixed(self.0)
1130 .mul_u128(180)
1131 .div(wide_pi(w), w)
1132 .round_to_i128_with(w, SCALE, mode)
1133 .expect("D38::to_degrees: result out of range");
1134 Self::from_bits(raw)
1135 }
1136
1137 #[inline]
1139 #[must_use]
1140 pub fn to_degrees_approx(self, working_digits: u32) -> Self {
1141 self.to_degrees_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
1142 }
1143
1144 #[inline]
1146 #[must_use]
1147 pub fn to_degrees_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
1148 if working_digits == crate::log_exp_strict::STRICT_GUARD {
1149 return self.to_degrees_strict_with(mode);
1150 }
1151 if self.0 == 0 {
1152 return Self::ZERO;
1153 }
1154 let w = SCALE + working_digits;
1155 let raw = to_fixed_w(self.0, working_digits)
1156 .mul_u128(180)
1157 .div(wide_pi(w), w)
1158 .round_to_i128_with(w, SCALE, mode)
1159 .expect("D38::to_degrees: result out of range");
1160 Self::from_bits(raw)
1161 }
1162
1163 #[inline]
1166 #[must_use]
1167 pub fn to_radians_strict(self) -> Self {
1168 self.to_radians_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
1169 }
1170
1171 #[inline]
1173 #[must_use]
1174 pub fn to_radians_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
1175 if self.0 == 0 {
1176 return Self::ZERO;
1177 }
1178 let w = SCALE + crate::log_exp_strict::STRICT_GUARD;
1179 let raw = to_fixed(self.0)
1180 .mul(wide_pi(w), w)
1181 .div_small(180)
1182 .round_to_i128_with(w, SCALE, mode)
1183 .expect("D38::to_radians: result out of range");
1184 Self::from_bits(raw)
1185 }
1186
1187 #[inline]
1189 #[must_use]
1190 pub fn to_radians_approx(self, working_digits: u32) -> Self {
1191 self.to_radians_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
1192 }
1193
1194 #[inline]
1196 #[must_use]
1197 pub fn to_radians_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
1198 if working_digits == crate::log_exp_strict::STRICT_GUARD {
1199 return self.to_radians_strict_with(mode);
1200 }
1201 if self.0 == 0 {
1202 return Self::ZERO;
1203 }
1204 let w = SCALE + working_digits;
1205 let raw = to_fixed_w(self.0, working_digits)
1206 .mul(wide_pi(w), w)
1207 .div_small(180)
1208 .round_to_i128_with(w, SCALE, mode)
1209 .expect("D38::to_radians: result out of range");
1210 Self::from_bits(raw)
1211 }
1212}
1213
1214
1215#[inline]
1274const fn small_x_linear_threshold<const SCALE: u32>() -> i128 {
1275 let thresh_exp = SCALE.saturating_sub((SCALE + 2) / 3);
1276 10_i128.pow(thresh_exp)
1277}
1278
1279fn wide_pi(w: u32) -> crate::d_w128_kernels::Fixed {
1280 debug_assert!(w <= 75, "wide_pi: working scale {w} exceeds embedded 75-digit π");
1281 let words = crate::consts::PI_RAW.0;
1284 let pi_at_75 = crate::d_w128_kernels::Fixed {
1285 negative: false,
1286 mag: [
1287 (words[0] as u128) | ((words[1] as u128) << 64),
1288 (words[2] as u128) | ((words[3] as u128) << 64),
1289 ],
1290 };
1291 if w == 75 {
1292 pi_at_75
1293 } else {
1294 pi_at_75.rescale_down(75, w)
1295 }
1296}
1297
1298fn wide_tau(w: u32) -> crate::d_w128_kernels::Fixed {
1300 wide_pi(w).double()
1301}
1302
1303fn wide_half_pi(w: u32) -> crate::d_w128_kernels::Fixed {
1305 wide_pi(w).halve()
1306}
1307
1308fn to_fixed(raw: i128) -> crate::d_w128_kernels::Fixed {
1311 to_fixed_w(raw, crate::log_exp_strict::STRICT_GUARD)
1312}
1313
1314fn to_fixed_w(raw: i128, working_digits: u32) -> crate::d_w128_kernels::Fixed {
1318 use crate::d_w128_kernels::Fixed;
1319 let m = Fixed::from_u128_mag(raw.unsigned_abs(), false)
1320 .mul_u128(10u128.pow(working_digits));
1321 if raw < 0 {
1322 m.neg()
1323 } else {
1324 m
1325 }
1326}
1327
1328fn atan2_kernel(
1333 y: crate::d_w128_kernels::Fixed,
1334 x: crate::d_w128_kernels::Fixed,
1335 y_raw: i128,
1336 w: u32,
1337) -> crate::d_w128_kernels::Fixed {
1338 use crate::d_w128_kernels::Fixed;
1339 if x.is_zero() {
1340 return if y_raw > 0 {
1341 wide_half_pi(w)
1342 } else if y_raw < 0 {
1343 wide_half_pi(w).neg()
1344 } else {
1345 Fixed::ZERO
1346 };
1347 }
1348 let abs_y_ge_abs_x = y.ge_mag(x);
1351 let base = if !abs_y_ge_abs_x {
1352 atan_fixed(y.div(x, w), w)
1353 } else {
1354 let inv = atan_fixed(x.div(y, w), w);
1355 let hp = wide_half_pi(w);
1356 let same_sign = y.negative == x.negative;
1357 if same_sign { hp.sub(inv) } else { hp.neg().sub(inv) }
1358 };
1359 if !x.negative {
1360 base
1361 } else if !y.negative {
1362 base.add(wide_pi(w))
1363 } else {
1364 base.sub(wide_pi(w))
1365 }
1366}
1367
1368fn sin_taylor(r: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
1371 let r2 = r.mul(r, w);
1372 let mut sum = r;
1373 let mut term = r; let mut k: u128 = 1;
1375 loop {
1376 term = term.mul(r2, w).div_small((2 * k) * (2 * k + 1));
1378 if term.is_zero() {
1379 break;
1380 }
1381 if k % 2 == 1 {
1382 sum = sum.sub(term);
1383 } else {
1384 sum = sum.add(term);
1385 }
1386 k += 1;
1387 if k > 200 {
1388 break;
1389 }
1390 }
1391 sum
1392}
1393
1394fn sin_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
1400 use crate::d_w128_kernels::Fixed;
1401 let tau = wide_tau(w);
1402 let pi = wide_pi(w);
1403 let half_pi = wide_half_pi(w);
1404
1405 let q = v_w.div(tau, w).round_to_nearest_int(w);
1407 let q_tau = if q >= 0 {
1408 tau.mul_u128(q as u128)
1409 } else {
1410 tau.mul_u128((-q) as u128).neg()
1411 };
1412 let r = v_w.sub(q_tau);
1413
1414 let sign = r.negative;
1416 let abs_r = Fixed { negative: false, mag: r.mag };
1417 let reduced = if abs_r.ge_mag(half_pi) {
1418 pi.sub(abs_r)
1419 } else {
1420 abs_r
1421 };
1422 let s = sin_taylor(reduced, w);
1423 if sign {
1424 s.neg()
1425 } else {
1426 s
1427 }
1428}
1429
1430fn atan_taylor(x: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
1433 let x2 = x.mul(x, w);
1434 let mut sum = x;
1435 let mut term = x; let mut k: u128 = 1;
1437 loop {
1438 term = term.mul(x2, w);
1439 let contrib = term.div_small(2 * k + 1);
1440 if contrib.is_zero() {
1441 break;
1442 }
1443 if k % 2 == 1 {
1444 sum = sum.sub(contrib);
1445 } else {
1446 sum = sum.add(contrib);
1447 }
1448 k += 1;
1449 if k > 300 {
1450 break;
1451 }
1452 }
1453 sum
1454}
1455
1456fn atan_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
1463 use crate::d_w128_kernels::Fixed;
1464
1465 #[cfg(feature = "perf-trace")]
1466 let _atan_span = ::tracing::info_span!("atan_fixed").entered();
1467
1468 #[cfg(feature = "perf-trace")]
1469 let _setup_span = ::tracing::info_span!("setup").entered();
1470 let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
1471 let sign = v_w.negative;
1472 let mut x = Fixed { negative: false, mag: v_w.mag };
1473 let mut add_half_pi = false;
1474 if x.ge_mag(one_w) && x != one_w {
1475 x = one_w.div(x, w); add_half_pi = true;
1477 }
1478 #[cfg(feature = "perf-trace")]
1479 drop(_setup_span);
1480
1481 #[cfg(feature = "perf-trace")]
1490 let _halvings_span = ::tracing::info_span!("halvings").entered();
1491 let halving_threshold = one_w.div_small(5); let mut halvings: u32 = 0;
1493 while x.ge_mag(halving_threshold) && halvings < 8 {
1494 let x2 = x.mul(x, w);
1495 let denom = one_w.add(one_w.add(x2).sqrt(w));
1496 x = x.div(denom, w);
1497 halvings += 1;
1498 }
1499 #[cfg(feature = "perf-trace")]
1500 drop(_halvings_span);
1501
1502 #[cfg(feature = "perf-trace")]
1503 let _taylor_span = ::tracing::info_span!("taylor").entered();
1504 let mut result = atan_taylor(x, w);
1505 #[cfg(feature = "perf-trace")]
1506 drop(_taylor_span);
1507
1508 #[cfg(feature = "perf-trace")]
1509 let _reasm_span = ::tracing::info_span!("reassemble").entered();
1510 result = result.shl(halvings);
1511 if add_half_pi {
1512 result = wide_half_pi(w).sub(result);
1513 }
1514 if sign {
1515 result.neg()
1516 } else {
1517 result
1518 }
1519}
1520
1521#[cfg(test)]
1522mod tests {
1523 use crate::consts::DecimalConsts;
1524 use crate::core_type::D38s12;
1525
1526 const TWO_LSB: i128 = 2;
1532
1533 const FOUR_LSB: i128 = 4;
1537
1538 const ANGLE_TOLERANCE_LSB: i128 = 32;
1546
1547 fn within_lsb(actual: D38s12, expected: D38s12, lsb: i128) -> bool {
1548 let diff = (actual.to_bits() - expected.to_bits()).abs();
1549 diff <= lsb
1550 }
1551
1552 #[cfg(all(feature = "strict", not(feature = "fast")))]
1561 #[test]
1562 fn strict_trig_family_matches_f64() {
1563 use crate::core_type::D38;
1564 macro_rules! check {
1565 ($name:literal, $raw:expr, $strict:expr, $f64expr:expr) => {{
1566 let strict: i128 = $strict;
1567 let v = $raw as f64 / 1e9;
1568 let reference = ($f64expr(v) * 1e9).round() as i128;
1569 assert!(
1570 (strict - reference).abs() <= 2,
1571 concat!($name, "({}) = {}, f64 reference {}"),
1572 $raw,
1573 strict,
1574 reference
1575 );
1576 }};
1577 }
1578 for &raw in &[
1580 -7_000_000_000_i128, -1_000_000_000, -100_000_000, 1,
1581 500_000_000, 1_000_000_000, 1_570_796_327, 3_000_000_000,
1582 6_283_185_307, 12_000_000_000,
1583 ] {
1584 let x = D38::<9>::from_bits(raw);
1585 check!("sin", raw, x.sin_strict().to_bits(), f64::sin);
1586 check!("cos", raw, x.cos_strict().to_bits(), f64::cos);
1587 check!("atan", raw, x.atan_strict().to_bits(), f64::atan);
1588 check!("sinh", raw, x.sinh_strict().to_bits(), f64::sinh);
1589 check!("cosh", raw, x.cosh_strict().to_bits(), f64::cosh);
1590 check!("tanh", raw, x.tanh_strict().to_bits(), f64::tanh);
1591 check!("asinh", raw, x.asinh_strict().to_bits(), f64::asinh);
1592 }
1593 for &raw in &[
1595 -1_000_000_000_i128, -700_000_000, -100_000_000, 0,
1596 250_000_000, 500_000_000, 999_999_999,
1597 ] {
1598 let x = D38::<9>::from_bits(raw);
1599 check!("asin", raw, x.asin_strict().to_bits(), f64::asin);
1600 check!("acos", raw, x.acos_strict().to_bits(), f64::acos);
1601 }
1602 for &raw in &[-900_000_000_i128, -300_000_000, 1, 300_000_000, 900_000_000] {
1604 let x = D38::<9>::from_bits(raw);
1605 check!("atanh", raw, x.atanh_strict().to_bits(), f64::atanh);
1606 }
1607 for &raw in &[1_000_000_000_i128, 1_500_000_000, 3_000_000_000, 50_000_000_000] {
1609 let x = D38::<9>::from_bits(raw);
1610 check!("acosh", raw, x.acosh_strict().to_bits(), f64::acosh);
1611 }
1612 for &raw in &[-1_000_000_000_i128, 1, 500_000_000, 1_000_000_000, 1_400_000_000] {
1614 let x = D38::<9>::from_bits(raw);
1615 check!("tan", raw, x.tan_strict().to_bits(), f64::tan);
1616 }
1617 }
1618
1619 #[test]
1621 fn sin_zero_is_zero() {
1622 assert_eq!(D38s12::ZERO.sin(), D38s12::ZERO);
1623 }
1624
1625 #[cfg(all(feature = "strict", not(feature = "fast")))]
1636 #[test]
1637 fn sin_one_correct_past_63_digit_pi_window() {
1638 use crate::core_type::D38;
1639 let expected_35: i128 = 84_147_098_480_789_650_665_250_232_163_029_900;
1642 let expected_37: i128 =
1645 8_414_709_848_078_965_066_525_023_216_302_989_996;
1646
1647 let got_35 = D38::<35>::ONE.sin_strict().to_bits();
1648 assert!(
1649 (got_35 - expected_35).abs() <= 1,
1650 "sin(1) @ D38<35>: got {got_35}, expected {expected_35}"
1651 );
1652
1653 let got_37 = D38::<37>::ONE.sin_strict().to_bits();
1654 assert!(
1655 (got_37 - expected_37).abs() <= 1,
1656 "sin(1) @ D38<37>: got {got_37}, expected {expected_37}"
1657 );
1658 }
1659
1660 #[test]
1662 fn cos_zero_is_one() {
1663 assert_eq!(D38s12::ZERO.cos(), D38s12::ONE);
1664 }
1665
1666 #[test]
1668 fn tan_zero_is_zero() {
1669 assert_eq!(D38s12::ZERO.tan(), D38s12::ZERO);
1670 }
1671
1672 #[test]
1676 fn sin_squared_plus_cos_squared_is_one() {
1677 for raw in [
1678 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, ] {
1684 let x = D38s12::from_bits(raw);
1685 let s = x.sin();
1686 let c = x.cos();
1687 let sum = (s * s) + (c * c);
1688 assert!(
1689 within_lsb(sum, D38s12::ONE, FOUR_LSB),
1690 "sin^2 + cos^2 != 1 for raw={raw}: got bits {} (delta {})",
1691 sum.to_bits(),
1692 (sum.to_bits() - D38s12::ONE.to_bits()).abs(),
1693 );
1694 }
1695 }
1696
1697 #[test]
1701 fn asin_zero_is_zero() {
1702 assert_eq!(D38s12::ZERO.asin(), D38s12::ZERO);
1703 }
1704
1705 #[test]
1707 fn acos_one_is_zero() {
1708 assert_eq!(D38s12::ONE.acos(), D38s12::ZERO);
1709 }
1710
1711 #[test]
1713 fn acos_zero_is_half_pi() {
1714 let result = D38s12::ZERO.acos();
1715 assert!(
1716 within_lsb(result, D38s12::half_pi(), FOUR_LSB),
1717 "acos(0) bits {}, half_pi bits {}",
1718 result.to_bits(),
1719 D38s12::half_pi().to_bits(),
1720 );
1721 }
1722
1723 #[test]
1725 fn atan_zero_is_zero() {
1726 assert_eq!(D38s12::ZERO.atan(), D38s12::ZERO);
1727 }
1728
1729 #[test]
1732 fn asin_of_sin_round_trip() {
1733 for raw in [
1734 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, ] {
1741 let x = D38s12::from_bits(raw);
1742 let recovered = x.sin().asin();
1743 assert!(
1744 within_lsb(recovered, x, FOUR_LSB),
1745 "asin(sin(x)) != x for raw={raw}: got bits {} (delta {})",
1746 recovered.to_bits(),
1747 (recovered.to_bits() - x.to_bits()).abs(),
1748 );
1749 }
1750 }
1751
1752 #[test]
1756 fn atan2_first_quadrant_diagonal() {
1757 let one = D38s12::ONE;
1758 let result = one.atan2(one);
1759 assert!(
1760 within_lsb(result, D38s12::quarter_pi(), TWO_LSB),
1761 "atan2(1, 1) bits {}, quarter_pi bits {}",
1762 result.to_bits(),
1763 D38s12::quarter_pi().to_bits(),
1764 );
1765 }
1766
1767 #[test]
1769 fn atan2_third_quadrant_diagonal() {
1770 let neg_one = -D38s12::ONE;
1771 let result = neg_one.atan2(neg_one);
1772 let three = D38s12::from_int(3);
1773 let expected = -(D38s12::quarter_pi() * three);
1774 assert!(
1775 within_lsb(result, expected, TWO_LSB),
1776 "atan2(-1, -1) bits {}, expected -3pi/4 bits {}",
1777 result.to_bits(),
1778 expected.to_bits(),
1779 );
1780 }
1781
1782 #[test]
1784 fn atan2_second_quadrant_diagonal() {
1785 let one = D38s12::ONE;
1786 let neg_one = -D38s12::ONE;
1787 let result = one.atan2(neg_one);
1788 let three = D38s12::from_int(3);
1789 let expected = D38s12::quarter_pi() * three;
1790 assert!(
1791 within_lsb(result, expected, TWO_LSB),
1792 "atan2(1, -1) bits {}, expected 3pi/4 bits {}",
1793 result.to_bits(),
1794 expected.to_bits(),
1795 );
1796 }
1797
1798 #[test]
1800 fn atan2_fourth_quadrant_diagonal() {
1801 let one = D38s12::ONE;
1802 let neg_one = -D38s12::ONE;
1803 let result = neg_one.atan2(one);
1804 let expected = -D38s12::quarter_pi();
1805 assert!(
1806 within_lsb(result, expected, TWO_LSB),
1807 "atan2(-1, 1) bits {}, expected -pi/4 bits {}",
1808 result.to_bits(),
1809 expected.to_bits(),
1810 );
1811 }
1812
1813 #[test]
1815 fn atan2_positive_x_axis_is_zero() {
1816 let zero = D38s12::ZERO;
1817 let one = D38s12::ONE;
1818 assert_eq!(zero.atan2(one), D38s12::ZERO);
1819 }
1820
1821 #[test]
1825 fn sinh_zero_is_zero() {
1826 assert_eq!(D38s12::ZERO.sinh(), D38s12::ZERO);
1827 }
1828
1829 #[test]
1831 fn cosh_zero_is_one() {
1832 assert_eq!(D38s12::ZERO.cosh(), D38s12::ONE);
1833 }
1834
1835 #[test]
1837 fn tanh_zero_is_zero() {
1838 assert_eq!(D38s12::ZERO.tanh(), D38s12::ZERO);
1839 }
1840
1841 #[test]
1843 fn asinh_zero_is_zero() {
1844 assert_eq!(D38s12::ZERO.asinh(), D38s12::ZERO);
1845 }
1846
1847 #[test]
1849 fn acosh_one_is_zero() {
1850 assert_eq!(D38s12::ONE.acosh(), D38s12::ZERO);
1851 }
1852
1853 #[test]
1855 fn atanh_zero_is_zero() {
1856 assert_eq!(D38s12::ZERO.atanh(), D38s12::ZERO);
1857 }
1858
1859 #[test]
1862 fn cosh_squared_minus_sinh_squared_is_one() {
1863 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1864 for raw in [
1865 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, ] {
1871 let x = D38s12::from_bits(raw);
1872 let ch = x.cosh();
1873 let sh = x.sinh();
1874 let diff = (ch * ch) - (sh * sh);
1875 assert!(
1876 within_lsb(diff, D38s12::ONE, FOUR_LSB),
1877 "cosh^2 - sinh^2 != 1 for raw={raw}: got bits {} (delta {})",
1878 diff.to_bits(),
1879 (diff.to_bits() - D38s12::ONE.to_bits()).abs(),
1880 );
1881 }
1882 }
1883
1884 #[test]
1890 fn to_degrees_pi_is_180() {
1891 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1892 let pi = D38s12::pi();
1893 let result = pi.to_degrees();
1894 let expected = D38s12::from_int(180);
1895 assert!(
1896 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1897 "to_degrees(pi) bits {}, expected 180 bits {} (delta {})",
1898 result.to_bits(),
1899 expected.to_bits(),
1900 (result.to_bits() - expected.to_bits()).abs(),
1901 );
1902 }
1903
1904 #[test]
1906 fn to_radians_180_is_pi() {
1907 let one_eighty = D38s12::from_int(180);
1908 let result = one_eighty.to_radians();
1909 let expected = D38s12::pi();
1910 assert!(
1911 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1912 "to_radians(180) bits {}, expected pi bits {} (delta {})",
1913 result.to_bits(),
1914 expected.to_bits(),
1915 (result.to_bits() - expected.to_bits()).abs(),
1916 );
1917 }
1918
1919 #[test]
1921 fn to_degrees_zero_is_zero() {
1922 assert_eq!(D38s12::ZERO.to_degrees(), D38s12::ZERO);
1923 }
1924
1925 #[test]
1927 fn to_radians_zero_is_zero() {
1928 assert_eq!(D38s12::ZERO.to_radians(), D38s12::ZERO);
1929 }
1930
1931 #[test]
1934 fn to_radians_to_degrees_round_trip() {
1935 for raw in [
1936 500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
1941 let x = D38s12::from_bits(raw);
1942 let recovered = x.to_degrees().to_radians();
1943 assert!(
1944 within_lsb(recovered, x, FOUR_LSB),
1945 "to_radians(to_degrees(x)) != x for raw={raw}: got bits {} (delta {})",
1946 recovered.to_bits(),
1947 (recovered.to_bits() - x.to_bits()).abs(),
1948 );
1949 }
1950 }
1951
1952 #[test]
1954 fn to_degrees_half_pi_is_90() {
1955 if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
1956 let result = D38s12::half_pi().to_degrees();
1957 let expected = D38s12::from_int(90);
1958 assert!(
1959 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1960 "to_degrees(half_pi) bits {}, expected 90 bits {} (delta {})",
1961 result.to_bits(),
1962 expected.to_bits(),
1963 (result.to_bits() - expected.to_bits()).abs(),
1964 );
1965 }
1966
1967 #[test]
1969 fn to_degrees_quarter_pi_is_45() {
1970 let result = D38s12::quarter_pi().to_degrees();
1971 let expected = D38s12::from_int(45);
1972 assert!(
1973 within_lsb(result, expected, ANGLE_TOLERANCE_LSB),
1974 "to_degrees(quarter_pi) bits {}, expected 45 bits {} (delta {})",
1975 result.to_bits(),
1976 expected.to_bits(),
1977 (result.to_bits() - expected.to_bits()).abs(),
1978 );
1979 }
1980
1981 #[test]
1986 fn tan_matches_sin_over_cos() {
1987 for raw in [
1988 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, ] {
1994 let x = D38s12::from_bits(raw);
1995 let t = x.tan();
1996 let sc = x.sin() / x.cos();
1997 assert!(
1998 within_lsb(t, sc, FOUR_LSB),
1999 "tan(x) != sin/cos for raw={raw}: tan bits {}, sin/cos bits {}",
2000 t.to_bits(),
2001 sc.to_bits(),
2002 );
2003 }
2004 }
2005
2006 #[test]
2009 fn tanh_matches_sinh_over_cosh() {
2010 for raw in [
2011 500_000_000_000_i128, -500_000_000_000_i128, 1_234_567_890_123_i128, -2_345_678_901_234_i128, ] {
2016 let x = D38s12::from_bits(raw);
2017 let t = x.tanh();
2018 let sc = x.sinh() / x.cosh();
2019 assert!(
2020 within_lsb(t, sc, FOUR_LSB),
2021 "tanh(x) != sinh/cosh for raw={raw}: tanh bits {}, sinh/cosh bits {}",
2022 t.to_bits(),
2023 sc.to_bits(),
2024 );
2025 }
2026 }
2027}
2028