1use crate::{
11 error::{assert_limited_precision, FpError},
12 fbig::FBig,
13 math::{
14 cache::{compute_e, reborrow_cache, ConstCache},
15 FpResult,
16 },
17 repr::{Context, Repr, Word},
18 round::{Round, Rounded},
19};
20use core::cmp::Ordering;
21use core::convert::TryFrom;
22use dashu_base::{AbsOrd, Approximation::Exact, RemEuclid, Sign::*};
23use dashu_int::IBig;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26enum Quadrant {
27 First,
28 Second,
29 Third,
30 Fourth,
31}
32
33fn signed_zero_normal<R: Round, const B: Word>(
36 ctx: &Context<R>,
37 x: &Repr<B>,
38) -> FpResult<FBig<R, B>> {
39 let zero = if x.is_neg_zero() {
40 Repr::neg_zero()
41 } else {
42 Repr::zero()
43 };
44 Ok(Exact(FBig::<R, B>::new(zero, *ctx)))
45}
46
47impl<R: Round> Context<R> {
48 fn compute_work_context_trig<const B: Word>(self, x: &Repr<B>) -> Self {
53 let x_mag = (x.exponent.saturating_add(x.digits_ub() as isize)).max(0) as usize;
55
56 let extra_guards = 50 + x_mag / 10;
60 let work_precision = self
61 .precision
62 .saturating_add(x_mag)
63 .saturating_add(extra_guards);
64 Self::new(work_precision)
65 }
66
67 fn reduce_to_quadrant<const B: Word>(
70 self,
71 x: &Repr<B>,
72 mut cache: Option<&mut ConstCache>,
73 ) -> (Self, FBig<R, B>, Quadrant) {
74 let work_context = self.compute_work_context_trig(x);
75 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
76
77 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
78 let half_pi = &pi / 2;
79 let x_scaled: FBig<R, B> = &x_f / &half_pi;
80 let k_f = x_scaled.round();
81 let r = k_f.fma(&half_pi, &x_f, Negative);
86 let k = IBig::try_from(k_f).expect("k_f is an exact integer or signed zero");
89
90 let k_mod_4_big = k.rem_euclid(IBig::from(4));
91 let Ok(k_mod_4_int) = i8::try_from(k_mod_4_big) else {
92 unreachable!("k % 4 is always in [0, 3]");
93 };
94 let quadrant = match k_mod_4_int {
95 0 => Quadrant::First,
96 1 => Quadrant::Second,
97 2 => Quadrant::Third,
98 3 => Quadrant::Fourth,
99 _ => unreachable!(),
100 };
101
102 (work_context, r, quadrant)
103 }
104
105 pub fn sin<const B: Word>(
107 &self,
108 x: &Repr<B>,
109 mut cache: Option<&mut ConstCache>,
110 ) -> FpResult<FBig<R, B>> {
111 if x.is_infinite() {
112 return Err(FpError::InfiniteInput);
113 }
114 assert_limited_precision(self.precision);
115
116 if x.significand.is_zero() {
117 return signed_zero_normal(self, x);
119 }
120
121 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
122
123 let res = match quadrant {
125 Quadrant::First => work_context.sin_internal(&r),
126 Quadrant::Second => work_context.cos_internal(&r),
127 Quadrant::Third => -work_context.sin_internal(&r),
128 Quadrant::Fourth => -work_context.cos_internal(&r),
129 };
130 Ok(res.with_precision(self.precision))
131 }
132
133 fn sin_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
135 if x.repr.significand.is_zero() {
136 return FBig::ZERO;
137 }
138 let x2 = x.sqr();
139 let mut sum = x.clone();
140 let mut term = x.clone();
141 let mut k = 1usize;
142 let threshold = sum.ulp_lb();
143 loop {
144 term *= &x2;
145 term /= (2 * k) * (2 * k + 1);
146 if term.abs_cmp(&threshold).is_le() {
147 break;
148 }
149 if k % 2 == 1 {
150 sum -= &term;
151 } else {
152 sum += &term;
153 }
154 k += 1;
155 }
156 sum
157 }
158
159 pub fn cos<const B: Word>(
161 &self,
162 x: &Repr<B>,
163 mut cache: Option<&mut ConstCache>,
164 ) -> FpResult<FBig<R, B>> {
165 if x.is_infinite() {
166 return Err(FpError::InfiniteInput);
167 }
168 assert_limited_precision(self.precision);
169
170 if x.significand.is_zero() {
171 return Ok(FBig::<R, B>::ONE.with_precision(self.precision));
173 }
174
175 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
176
177 let res = match quadrant {
179 Quadrant::First => work_context.cos_internal(&r),
180 Quadrant::Second => -work_context.sin_internal(&r),
181 Quadrant::Third => -work_context.cos_internal(&r),
182 Quadrant::Fourth => work_context.sin_internal(&r),
183 };
184 Ok(res.with_precision(self.precision))
185 }
186
187 fn cos_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
189 if x.repr.significand.is_zero() {
190 return FBig::ONE.with_precision(self.precision).value();
191 }
192 let x2 = x.sqr();
193 let mut sum = FBig::<R, B>::ONE.with_precision(self.precision).value();
194 let mut term = sum.clone();
195 let mut k = 1usize;
196 let threshold = sum.ulp_lb();
197 loop {
198 term *= &x2;
199 term /= (2 * k) * (2 * k - 1);
200 if term.abs_cmp(&threshold).is_le() {
201 break;
202 }
203 if k % 2 == 1 {
204 sum -= &term;
205 } else {
206 sum += &term;
207 }
208 k += 1;
209 }
210 sum
211 }
212
213 pub fn sin_cos<const B: Word>(
217 &self,
218 x: &Repr<B>,
219 mut cache: Option<&mut ConstCache>,
220 ) -> (FpResult<FBig<R, B>>, FpResult<FBig<R, B>>) {
221 if x.is_infinite() {
222 return (Err(FpError::InfiniteInput), Err(FpError::InfiniteInput));
223 }
224 assert_limited_precision(self.precision);
225
226 if x.significand.is_zero() {
227 let s = signed_zero_normal(self, x);
229 let c = Ok(FBig::<R, B>::ONE.with_precision(self.precision));
230 return (s, c);
231 }
232
233 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
234
235 let (sin_r, cos_r) = work_context.sin_cos_internal(&r);
236
237 let (s, c) = match quadrant {
238 Quadrant::First => (sin_r, cos_r),
239 Quadrant::Second => (cos_r, -sin_r),
240 Quadrant::Third => (-sin_r, -cos_r),
241 Quadrant::Fourth => (-cos_r, sin_r),
242 };
243
244 (Ok(s.with_precision(self.precision)), Ok(c.with_precision(self.precision)))
245 }
246
247 pub(crate) fn sin_cos_internal<const B: Word>(
249 self,
250 x: &FBig<R, B>,
251 ) -> (FBig<R, B>, FBig<R, B>) {
252 if x.repr.significand.is_zero() {
253 return (FBig::ZERO, FBig::ONE.with_precision(self.precision).value());
254 }
255 let x2 = x.sqr();
256 let mut sin_sum = x.clone();
257 let mut cos_sum = FBig::<R, B>::ONE.with_precision(self.precision).value();
258 let mut sin_term = x.clone();
259 let mut cos_term = cos_sum.clone();
260 let mut k = 1usize;
261 let sin_threshold = sin_sum.ulp_lb();
262 let cos_threshold = cos_sum.ulp_lb();
263 loop {
264 cos_term *= &x2;
265 cos_term /= (2 * k) * (2 * k - 1);
266 sin_term *= &x2;
267 sin_term /= (2 * k) * (2 * k + 1);
268
269 if sin_term.abs_cmp(&sin_threshold).is_le() && cos_term.abs_cmp(&cos_threshold).is_le()
270 {
271 break;
272 }
273
274 if k % 2 == 1 {
275 cos_sum -= &cos_term;
276 sin_sum -= &sin_term;
277 } else {
278 cos_sum += &cos_term;
279 sin_sum += &sin_term;
280 }
281 k += 1;
282 }
283 (sin_sum, cos_sum)
284 }
285
286 pub fn tan<const B: Word>(
291 &self,
292 x: &Repr<B>,
293 mut cache: Option<&mut ConstCache>,
294 ) -> FpResult<FBig<R, B>> {
295 if x.is_infinite() {
296 return Err(FpError::InfiniteInput);
297 }
298 assert_limited_precision(self.precision);
299
300 if x.significand.is_zero() {
301 return signed_zero_normal(self, x);
303 }
304
305 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
306 let (sin_r, cos_r) = work_context.sin_cos_internal(&r);
307
308 let (s_f, c_f) = match quadrant {
309 Quadrant::First => (sin_r, cos_r),
310 Quadrant::Second => (cos_r, -sin_r),
311 Quadrant::Third => (-sin_r, -cos_r),
312 Quadrant::Fourth => (-cos_r, sin_r),
313 };
314
315 if c_f.repr.is_pos_zero() {
316 let inf = if s_f.sign() == Negative {
318 Repr::neg_infinity()
319 } else {
320 Repr::infinity()
321 };
322 return Ok(Rounded::Exact(FBig::new(inf, *self)));
323 }
324 self.div(&s_f.repr, &c_f.repr)
325 .map(|r| r.and_then(|f| f.with_precision(self.precision)))
326 }
327
328 pub fn asin<const B: Word>(
334 &self,
335 x: &Repr<B>,
336 mut cache: Option<&mut ConstCache>,
337 ) -> FpResult<FBig<R, B>> {
338 if x.is_infinite() {
339 return Err(FpError::InfiniteInput);
340 }
341 assert_limited_precision(self.precision);
342
343 let x_orig = FBig::<R, B>::new(x.clone(), *self);
344 if x_orig.abs_cmp(&FBig::ONE).is_gt() {
346 return Err(FpError::OutOfDomain);
347 }
348
349 let guard_digits = 50;
350 let work_precision = self.precision + guard_digits;
351 let work_context = Self::new(work_precision);
352
353 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
354
355 let res = work_context.asin_internal(&x_f, reborrow_cache(&mut cache));
356 Ok(res.with_precision(self.precision))
357 }
358
359 fn asin_internal<const B: Word>(
360 self,
361 x_f: &FBig<R, B>,
362 mut cache: Option<&mut ConstCache>,
363 ) -> FBig<R, B> {
364 let one = FBig::<R, B>::ONE.with_precision(self.precision).value();
365 let x2 = x_f.sqr();
366 let d = self.unwrap_fp(self.sqrt(&(one - x2).repr));
367
368 if d.repr.is_pos_zero() || d.repr.is_neg_zero() {
369 let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
374 let half_pi: FBig<R, B> = pi / 2;
375 if x_f.sign() == Positive {
376 return half_pi;
377 }
378 return -half_pi;
379 }
380
381 self.atan_with_reduction(&(x_f / d), reborrow_cache(&mut cache))
382 }
383
384 pub fn acos<const B: Word>(
390 &self,
391 x: &Repr<B>,
392 mut cache: Option<&mut ConstCache>,
393 ) -> FpResult<FBig<R, B>> {
394 if x.is_infinite() {
395 return Err(FpError::InfiniteInput);
396 }
397 assert_limited_precision(self.precision);
398
399 let x_orig = FBig::<R, B>::new(x.clone(), *self);
400 if x_orig.abs_cmp(&FBig::ONE).is_gt() {
402 return Err(FpError::OutOfDomain);
403 }
404
405 let guard_digits = 50;
406 let work_precision = self.precision + guard_digits;
407 let work_context = Self::new(work_precision);
408
409 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
410
411 let asin_x = work_context.asin_internal(&x_f, reborrow_cache(&mut cache));
412 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
413 let half_pi: FBig<R, B> = pi / 2;
414 let res: FBig<R, B> = half_pi - asin_x;
415 Ok(res.with_precision(self.precision))
416 }
417
418 pub fn atan<const B: Word>(
420 &self,
421 x: &Repr<B>,
422 mut cache: Option<&mut ConstCache>,
423 ) -> FpResult<FBig<R, B>> {
424 if x.is_infinite() {
425 let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
427 let half_pi: FBig<R, B> = pi / 2;
428 let res: FBig<R, B> = if x.sign() == Positive {
429 half_pi
430 } else {
431 -half_pi
432 };
433 return Ok(res.with_precision(self.precision));
434 }
435
436 assert_limited_precision(self.precision);
437
438 if x.significand.is_zero() {
439 return signed_zero_normal(self, x);
441 }
442
443 let guard_digits = 50;
444 let work_precision = self.precision + guard_digits;
445 let work_context = Self::new(work_precision);
446
447 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
448 let res = work_context.atan_with_reduction(&x_f, reborrow_cache(&mut cache));
449 Ok(res.with_precision(self.precision))
450 }
451
452 fn atan_with_reduction<const B: Word>(
454 self,
455 x_f: &FBig<R, B>,
456 mut cache: Option<&mut ConstCache>,
457 ) -> FBig<R, B> {
458 let sign = x_f.sign();
459 let mut x_abs = x_f.clone();
460 if sign == Negative {
461 x_abs = -x_abs;
462 }
463 let mut res = if x_abs >= FBig::<R, B>::ONE.with_precision(self.precision).value() {
464 let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
465 let inv_x = FBig::<R, B>::ONE.with_precision(self.precision).value() / x_abs;
466 (pi / 2) - self.atan_internal(&inv_x)
467 } else {
468 self.atan_internal(&x_abs)
469 };
470 if sign == Negative {
471 res = -res;
472 }
473 res
474 }
475
476 fn atan_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
479 let x2 = x.sqr();
481 let one_plus_x2 = FBig::ONE + &x2;
482 let mut term = x / &one_plus_x2;
483 let mut sum = term.clone();
484 let factor = (2 * &x2) / one_plus_x2;
485 let mut n = 1usize;
486 let threshold = sum.ulp_lb();
487 loop {
488 term *= &factor;
489 term *= n;
490 term /= 2 * n + 1;
491 if term.abs_cmp(&threshold).is_le() {
492 break;
493 }
494 sum += &term;
495 n += 1;
496 }
497 sum
498 }
499
500 pub fn atan2<const B: Word>(
505 &self,
506 y: &Repr<B>,
507 x: &Repr<B>,
508 mut cache: Option<&mut ConstCache>,
509 ) -> FpResult<FBig<R, B>> {
510 if y.is_finite() && x.is_finite() && y.significand.is_zero() && x.significand.is_zero() {
511 return Err(FpError::OutOfDomain);
512 }
513
514 assert_limited_precision(self.precision);
515
516 let guard_digits = 50;
517 let work_precision = self.precision + guard_digits;
518 let work_context = Self::new(work_precision);
519
520 if y.is_infinite() || x.is_infinite() {
522 let (sy, sx) = (y.sign() == Positive, x.sign() == Positive);
523 let res: FBig<R, B> = match (y.is_infinite(), x.is_infinite(), sy, sx) {
524 (true, true, true, true) => {
525 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 4
526 }
527 (true, true, true, false) => {
528 work_context.pi::<B>(reborrow_cache(&mut cache)).value() * 3 / 4
529 }
530 (true, true, false, true) => {
531 let pi4: FBig<R, B> =
532 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 4;
533 -pi4
534 }
535 (true, true, false, false) => {
536 let pi34: FBig<R, B> =
537 work_context.pi::<B>(reborrow_cache(&mut cache)).value() * 3 / 4;
538 -pi34
539 }
540 (true, false, true, _) => {
541 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 2
542 }
543 (true, false, false, _) => {
544 let half_pi: FBig<R, B> =
545 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 2;
546 -half_pi
547 }
548 (false, true, _, true) => {
549 if sy {
551 FBig::<R, B>::ZERO.with_precision(work_precision).value()
552 } else {
553 FBig::<R, B>::new(Repr::neg_zero(), work_context)
554 .with_precision(work_precision)
555 .value()
556 }
557 }
558 (false, true, true, false) => {
559 work_context.pi::<B>(reborrow_cache(&mut cache)).value()
560 }
561 (false, true, false, false) => {
562 -work_context.pi::<B>(reborrow_cache(&mut cache)).value()
563 }
564 _ => unreachable!(),
565 };
566 return Ok(res.with_precision(self.precision));
567 }
568
569 let y_f = FBig::<R, B>::new(work_context.repr_round(y.clone()).value(), work_context);
570 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
571
572 match x_f.cmp(&FBig::<R, B>::ZERO) {
573 Ordering::Greater => {
574 let res =
575 work_context.atan_with_reduction(&(y_f / x_f), reborrow_cache(&mut cache));
576 Ok(res.with_precision(self.precision))
577 }
578 Ordering::Less => {
579 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
580 let y_sign = y_f.sign();
581 let atan_yx =
582 work_context.atan_with_reduction(&(y_f / x_f), reborrow_cache(&mut cache));
583 let res = if y_sign == Positive {
584 atan_yx + pi
585 } else {
586 atan_yx - pi
587 };
588 Ok(res.with_precision(self.precision))
589 }
590 Ordering::Equal => {
591 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
593 let half_pi: FBig<R, B> = pi / 2;
594 if y_f > FBig::<R, B>::ZERO {
595 Ok(half_pi.with_precision(self.precision))
596 } else {
597 let res = -half_pi;
598 Ok(res.with_precision(self.precision))
599 }
600 }
601 }
602 }
603}
604
605impl<R: Round, const B: Word> FBig<R, B> {
606 #[inline]
611 pub fn sin(&self) -> Self {
612 self.context.unwrap_fp(self.context.sin(&self.repr, None))
613 }
614
615 #[inline]
620 pub fn cos(&self) -> Self {
621 self.context.unwrap_fp(self.context.cos(&self.repr, None))
622 }
623
624 #[inline]
631 pub fn sin_cos(&self) -> (Self, Self) {
632 let (s, c) = self.context.sin_cos(&self.repr, None);
633 (self.context.unwrap_fp(s), self.context.unwrap_fp(c))
634 }
635
636 #[inline]
643 pub fn tan(&self) -> Self {
644 self.context.unwrap_fp(self.context.tan(&self.repr, None))
645 }
646
647 #[inline]
652 pub fn asin(&self) -> Self {
653 self.context.unwrap_fp(self.context.asin(&self.repr, None))
654 }
655
656 #[inline]
661 pub fn acos(&self) -> Self {
662 self.context.unwrap_fp(self.context.acos(&self.repr, None))
663 }
664
665 #[inline]
667 pub fn atan(&self) -> Self {
668 self.context.unwrap_fp(self.context.atan(&self.repr, None))
669 }
670
671 #[inline]
676 pub fn atan2(&self, x: &Self) -> Self {
677 self.context
678 .unwrap_fp(self.context.atan2(&self.repr, &x.repr, None))
679 }
680}
681
682impl<R: Round> Context<R> {
683 #[must_use]
695 pub fn pi<const B: Word>(&self, cache: Option<&mut ConstCache>) -> Rounded<FBig<R, B>> {
696 if let Some(c) = cache {
697 return c.pi::<B, R>(self.precision);
698 }
699
700 let mut fresh = ConstCache::new();
704 fresh.pi::<B, R>(self.precision)
705 }
706
707 #[must_use]
722 pub fn e<const B: Word>(&self) -> Rounded<FBig<R, B>> {
723 compute_e::<B, R>(self.precision)
724 }
725}
726
727impl<R: Round, const B: Word> FBig<R, B> {
728 #[inline]
730 #[must_use]
731 pub fn pi(precision: usize) -> Self {
732 Context::<R>::new(precision).pi(None).value()
733 }
734
735 #[inline]
747 #[must_use]
748 pub fn e(precision: usize) -> Self {
749 Context::<R>::new(precision).e::<B>().value()
750 }
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756 use crate::round::mode;
757 use crate::DBig;
758 use core::str::FromStr;
759
760 #[test]
761 fn test_atan_infinity_is_preserved() {
762 let ctx = Context::<mode::HalfEven>::new(53);
763 let r = ctx.atan::<2>(&Repr::<2>::infinity(), None).unwrap().value();
765 assert!(r.repr().sign() == Positive);
766 assert!(r > FBig::<mode::HalfEven>::ONE);
768 }
769
770 #[test]
774 fn test_trig_tiny_negative_no_panic() {
775 let ctx = Context::<mode::HalfAway>::new(30);
776 for &e in &[-1isize, -2, -10, -30] {
777 let x = Repr::<10>::new(IBig::from(-1), e);
779 let s = ctx.sin::<10>(&x, None).unwrap().value();
780 let c = ctx.cos::<10>(&x, None).unwrap().value();
781 let (ss, cc) = ctx.sin_cos::<10>(&x, None);
782 let ss = ss.unwrap().value();
783 let cc = cc.unwrap().value();
784 assert_eq!(s.sign(), Negative);
786 assert_eq!(c.sign(), Positive);
787 assert_eq!(ss.sign(), Negative);
788 assert_eq!(cc.sign(), Positive);
789 }
790 }
791
792 #[test]
796 fn test_sin_many_digit_rounding_no_panic() {
797 let x = DBig::from_str("-5.525474318981006776603409487767135633516667011547942409467e-3")
798 .unwrap();
799 let ctx = Context::<mode::HalfEven>::new(100);
800 let s = ctx.sin::<10>(x.repr(), None).unwrap().value();
801 assert_eq!(s.sign(), Negative);
803 }
804}