1use crate::{
10 error::{assert_limited_precision, FpError},
11 fbig::FBig,
12 math::{
13 cache::{reborrow_cache, ConstCache},
14 FpResult,
15 },
16 repr::{Context, Repr, Word},
17 round::{Round, Rounded},
18};
19use core::cmp::Ordering;
20use core::convert::TryFrom;
21use dashu_base::{AbsOrd, Approximation::Exact, RemEuclid, Sign};
22use dashu_int::IBig;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25enum Quadrant {
26 First,
27 Second,
28 Third,
29 Fourth,
30}
31
32fn signed_zero_normal<R: Round, const B: Word>(
35 ctx: &Context<R>,
36 x: &Repr<B>,
37) -> FpResult<FBig<R, B>> {
38 let zero = if x.is_neg_zero() {
39 Repr::neg_zero()
40 } else {
41 Repr::zero()
42 };
43 Ok(Exact(FBig::<R, B>::new(zero, *ctx)))
44}
45
46impl<R: Round> Context<R> {
47 fn compute_work_context_trig<const B: Word>(self, x: &Repr<B>) -> Self {
52 let x_mag = (x.exponent.saturating_add(x.digits_ub() as isize)).max(0) as usize;
54
55 let extra_guards = 50 + x_mag / 10;
59 let work_precision = self
60 .precision
61 .saturating_add(x_mag)
62 .saturating_add(extra_guards);
63 Self::new(work_precision)
64 }
65
66 fn reduce_to_quadrant<const B: Word>(
69 self,
70 x: &Repr<B>,
71 mut cache: Option<&mut ConstCache>,
72 ) -> (Self, FBig<R, B>, Quadrant) {
73 let work_context = self.compute_work_context_trig(x);
74 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
75
76 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
77 let half_pi = &pi / 2;
78 let x_scaled: FBig<R, B> = &x_f / &half_pi;
79 let k_f = x_scaled.round();
80 let r = x_f - &k_f * half_pi;
81 let k = IBig::try_from(k_f).expect("k_f is an exact integer or signed zero");
84
85 let k_mod_4_big = k.rem_euclid(IBig::from(4));
86 let Ok(k_mod_4_int) = i8::try_from(k_mod_4_big) else {
87 unreachable!("k % 4 is always in [0, 3]");
88 };
89 let quadrant = match k_mod_4_int {
90 0 => Quadrant::First,
91 1 => Quadrant::Second,
92 2 => Quadrant::Third,
93 3 => Quadrant::Fourth,
94 _ => unreachable!(),
95 };
96
97 (work_context, r, quadrant)
98 }
99
100 pub fn sin<const B: Word>(
102 &self,
103 x: &Repr<B>,
104 mut cache: Option<&mut ConstCache>,
105 ) -> FpResult<FBig<R, B>> {
106 if x.is_infinite() {
107 return Err(FpError::InfiniteInput);
108 }
109 assert_limited_precision(self.precision);
110
111 if x.significand.is_zero() {
112 return signed_zero_normal(self, x);
114 }
115
116 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
117
118 let res = match quadrant {
120 Quadrant::First => work_context.sin_internal(&r),
121 Quadrant::Second => work_context.cos_internal(&r),
122 Quadrant::Third => -work_context.sin_internal(&r),
123 Quadrant::Fourth => -work_context.cos_internal(&r),
124 };
125 Ok(res.with_precision(self.precision))
126 }
127
128 fn sin_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
130 if x.repr.significand.is_zero() {
131 return FBig::ZERO;
132 }
133 let x2 = x.sqr();
134 let mut sum = x.clone();
135 let mut term = x.clone();
136 let mut k = 1usize;
137 let threshold = sum.sub_ulp();
138 loop {
139 term *= &x2;
140 term /= (2 * k) * (2 * k + 1);
141 if term.abs_cmp(&threshold).is_le() {
142 break;
143 }
144 if k % 2 == 1 {
145 sum -= &term;
146 } else {
147 sum += &term;
148 }
149 k += 1;
150 }
151 sum
152 }
153
154 pub fn cos<const B: Word>(
156 &self,
157 x: &Repr<B>,
158 mut cache: Option<&mut ConstCache>,
159 ) -> FpResult<FBig<R, B>> {
160 if x.is_infinite() {
161 return Err(FpError::InfiniteInput);
162 }
163 assert_limited_precision(self.precision);
164
165 if x.significand.is_zero() {
166 return Ok(FBig::<R, B>::ONE.with_precision(self.precision));
168 }
169
170 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
171
172 let res = match quadrant {
174 Quadrant::First => work_context.cos_internal(&r),
175 Quadrant::Second => -work_context.sin_internal(&r),
176 Quadrant::Third => -work_context.cos_internal(&r),
177 Quadrant::Fourth => work_context.sin_internal(&r),
178 };
179 Ok(res.with_precision(self.precision))
180 }
181
182 fn cos_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
184 if x.repr.significand.is_zero() {
185 return FBig::ONE.with_precision(self.precision).value();
186 }
187 let x2 = x.sqr();
188 let mut sum = FBig::<R, B>::ONE.with_precision(self.precision).value();
189 let mut term = sum.clone();
190 let mut k = 1usize;
191 let threshold = sum.sub_ulp();
192 loop {
193 term *= &x2;
194 term /= (2 * k) * (2 * k - 1);
195 if term.abs_cmp(&threshold).is_le() {
196 break;
197 }
198 if k % 2 == 1 {
199 sum -= &term;
200 } else {
201 sum += &term;
202 }
203 k += 1;
204 }
205 sum
206 }
207
208 pub fn sin_cos<const B: Word>(
212 &self,
213 x: &Repr<B>,
214 mut cache: Option<&mut ConstCache>,
215 ) -> (FpResult<FBig<R, B>>, FpResult<FBig<R, B>>) {
216 if x.is_infinite() {
217 return (Err(FpError::InfiniteInput), Err(FpError::InfiniteInput));
218 }
219 assert_limited_precision(self.precision);
220
221 if x.significand.is_zero() {
222 let s = signed_zero_normal(self, x);
224 let c = Ok(FBig::<R, B>::ONE.with_precision(self.precision));
225 return (s, c);
226 }
227
228 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
229
230 let (sin_r, cos_r) = work_context.sin_cos_internal(&r);
231
232 let (s, c) = match quadrant {
233 Quadrant::First => (sin_r, cos_r),
234 Quadrant::Second => (cos_r, -sin_r),
235 Quadrant::Third => (-sin_r, -cos_r),
236 Quadrant::Fourth => (-cos_r, sin_r),
237 };
238
239 (Ok(s.with_precision(self.precision)), Ok(c.with_precision(self.precision)))
240 }
241
242 pub(crate) fn sin_cos_internal<const B: Word>(
244 self,
245 x: &FBig<R, B>,
246 ) -> (FBig<R, B>, FBig<R, B>) {
247 if x.repr.significand.is_zero() {
248 return (FBig::ZERO, FBig::ONE.with_precision(self.precision).value());
249 }
250 let x2 = x.sqr();
251 let mut sin_sum = x.clone();
252 let mut cos_sum = FBig::<R, B>::ONE.with_precision(self.precision).value();
253 let mut sin_term = x.clone();
254 let mut cos_term = cos_sum.clone();
255 let mut k = 1usize;
256 let sin_threshold = sin_sum.sub_ulp();
257 let cos_threshold = cos_sum.sub_ulp();
258 loop {
259 cos_term *= &x2;
260 cos_term /= (2 * k) * (2 * k - 1);
261 sin_term *= &x2;
262 sin_term /= (2 * k) * (2 * k + 1);
263
264 if sin_term.abs_cmp(&sin_threshold).is_le() && cos_term.abs_cmp(&cos_threshold).is_le()
265 {
266 break;
267 }
268
269 if k % 2 == 1 {
270 cos_sum -= &cos_term;
271 sin_sum -= &sin_term;
272 } else {
273 cos_sum += &cos_term;
274 sin_sum += &sin_term;
275 }
276 k += 1;
277 }
278 (sin_sum, cos_sum)
279 }
280
281 pub fn tan<const B: Word>(
286 &self,
287 x: &Repr<B>,
288 mut cache: Option<&mut ConstCache>,
289 ) -> FpResult<FBig<R, B>> {
290 if x.is_infinite() {
291 return Err(FpError::InfiniteInput);
292 }
293 assert_limited_precision(self.precision);
294
295 if x.significand.is_zero() {
296 return signed_zero_normal(self, x);
298 }
299
300 let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
301 let (sin_r, cos_r) = work_context.sin_cos_internal(&r);
302
303 let (s_f, c_f) = match quadrant {
304 Quadrant::First => (sin_r, cos_r),
305 Quadrant::Second => (cos_r, -sin_r),
306 Quadrant::Third => (-sin_r, -cos_r),
307 Quadrant::Fourth => (-cos_r, sin_r),
308 };
309
310 if c_f.repr.is_pos_zero() {
311 let inf = if s_f.sign() == Sign::Negative {
313 Repr::neg_infinity()
314 } else {
315 Repr::infinity()
316 };
317 return Ok(Rounded::Exact(FBig::new(inf, *self)));
318 }
319 self.div(&s_f.repr, &c_f.repr)
320 .map(|r| r.and_then(|f| f.with_precision(self.precision)))
321 }
322
323 pub fn asin<const B: Word>(
329 &self,
330 x: &Repr<B>,
331 mut cache: Option<&mut ConstCache>,
332 ) -> FpResult<FBig<R, B>> {
333 if x.is_infinite() {
334 return Err(FpError::InfiniteInput);
335 }
336 assert_limited_precision(self.precision);
337
338 let x_orig = FBig::<R, B>::new(x.clone(), *self);
339 if x_orig.abs_cmp(&FBig::ONE).is_gt() {
341 return Err(FpError::OutOfDomain);
342 }
343
344 let guard_digits = 50;
345 let work_precision = self.precision + guard_digits;
346 let work_context = Self::new(work_precision);
347
348 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
349
350 let res = work_context.asin_internal(&x_f, reborrow_cache(&mut cache));
351 Ok(res.with_precision(self.precision))
352 }
353
354 fn asin_internal<const B: Word>(
355 self,
356 x_f: &FBig<R, B>,
357 mut cache: Option<&mut ConstCache>,
358 ) -> FBig<R, B> {
359 let one = FBig::<R, B>::ONE.with_precision(self.precision).value();
360 let x2 = x_f.sqr();
361 let d = self.unwrap_fp(self.sqrt(&(one - x2).repr));
362
363 if d.repr.is_pos_zero() || d.repr.is_neg_zero() {
364 let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
369 let half_pi: FBig<R, B> = pi / 2;
370 if x_f.sign() == Sign::Positive {
371 return half_pi;
372 }
373 return -half_pi;
374 }
375
376 self.atan_with_reduction(&(x_f / d), reborrow_cache(&mut cache))
377 }
378
379 pub fn acos<const B: Word>(
385 &self,
386 x: &Repr<B>,
387 mut cache: Option<&mut ConstCache>,
388 ) -> FpResult<FBig<R, B>> {
389 if x.is_infinite() {
390 return Err(FpError::InfiniteInput);
391 }
392 assert_limited_precision(self.precision);
393
394 let x_orig = FBig::<R, B>::new(x.clone(), *self);
395 if x_orig.abs_cmp(&FBig::ONE).is_gt() {
397 return Err(FpError::OutOfDomain);
398 }
399
400 let guard_digits = 50;
401 let work_precision = self.precision + guard_digits;
402 let work_context = Self::new(work_precision);
403
404 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
405
406 let asin_x = work_context.asin_internal(&x_f, reborrow_cache(&mut cache));
407 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
408 let half_pi: FBig<R, B> = pi / 2;
409 let res: FBig<R, B> = half_pi - asin_x;
410 Ok(res.with_precision(self.precision))
411 }
412
413 pub fn atan<const B: Word>(
415 &self,
416 x: &Repr<B>,
417 mut cache: Option<&mut ConstCache>,
418 ) -> FpResult<FBig<R, B>> {
419 if x.is_infinite() {
420 let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
422 let half_pi: FBig<R, B> = pi / 2;
423 let res: FBig<R, B> = if x.sign() == Sign::Positive {
424 half_pi
425 } else {
426 -half_pi
427 };
428 return Ok(res.with_precision(self.precision));
429 }
430
431 assert_limited_precision(self.precision);
432
433 if x.significand.is_zero() {
434 return signed_zero_normal(self, x);
436 }
437
438 let guard_digits = 50;
439 let work_precision = self.precision + guard_digits;
440 let work_context = Self::new(work_precision);
441
442 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
443 let res = work_context.atan_with_reduction(&x_f, reborrow_cache(&mut cache));
444 Ok(res.with_precision(self.precision))
445 }
446
447 fn atan_with_reduction<const B: Word>(
449 self,
450 x_f: &FBig<R, B>,
451 mut cache: Option<&mut ConstCache>,
452 ) -> FBig<R, B> {
453 let sign = x_f.sign();
454 let mut x_abs = x_f.clone();
455 if sign == Sign::Negative {
456 x_abs = -x_abs;
457 }
458 let mut res = if x_abs >= FBig::<R, B>::ONE.with_precision(self.precision).value() {
459 let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
460 let inv_x = FBig::<R, B>::ONE.with_precision(self.precision).value() / x_abs;
461 (pi / 2) - self.atan_internal(&inv_x)
462 } else {
463 self.atan_internal(&x_abs)
464 };
465 if sign == Sign::Negative {
466 res = -res;
467 }
468 res
469 }
470
471 fn atan_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
474 let x2 = x.sqr();
476 let one_plus_x2 = FBig::ONE + &x2;
477 let mut term = x / &one_plus_x2;
478 let mut sum = term.clone();
479 let factor = (2 * &x2) / one_plus_x2;
480 let mut n = 1usize;
481 let threshold = sum.sub_ulp();
482 loop {
483 term *= &factor;
484 term *= n;
485 term /= 2 * n + 1;
486 if term.abs_cmp(&threshold).is_le() {
487 break;
488 }
489 sum += &term;
490 n += 1;
491 }
492 sum
493 }
494
495 pub fn atan2<const B: Word>(
500 &self,
501 y: &Repr<B>,
502 x: &Repr<B>,
503 mut cache: Option<&mut ConstCache>,
504 ) -> FpResult<FBig<R, B>> {
505 if y.is_finite() && x.is_finite() && y.significand.is_zero() && x.significand.is_zero() {
506 return Err(FpError::OutOfDomain);
507 }
508
509 assert_limited_precision(self.precision);
510
511 let guard_digits = 50;
512 let work_precision = self.precision + guard_digits;
513 let work_context = Self::new(work_precision);
514
515 if y.is_infinite() || x.is_infinite() {
517 let (sy, sx) = (y.sign() == Sign::Positive, x.sign() == Sign::Positive);
518 let res: FBig<R, B> = match (y.is_infinite(), x.is_infinite(), sy, sx) {
519 (true, true, true, true) => {
520 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 4
521 }
522 (true, true, true, false) => {
523 work_context.pi::<B>(reborrow_cache(&mut cache)).value() * 3 / 4
524 }
525 (true, true, false, true) => {
526 let pi4: FBig<R, B> =
527 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 4;
528 -pi4
529 }
530 (true, true, false, false) => {
531 let pi34: FBig<R, B> =
532 work_context.pi::<B>(reborrow_cache(&mut cache)).value() * 3 / 4;
533 -pi34
534 }
535 (true, false, true, _) => {
536 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 2
537 }
538 (true, false, false, _) => {
539 let half_pi: FBig<R, B> =
540 work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 2;
541 -half_pi
542 }
543 (false, true, _, true) => {
544 if sy {
546 FBig::<R, B>::ZERO.with_precision(work_precision).value()
547 } else {
548 FBig::<R, B>::new(Repr::neg_zero(), work_context)
549 .with_precision(work_precision)
550 .value()
551 }
552 }
553 (false, true, true, false) => {
554 work_context.pi::<B>(reborrow_cache(&mut cache)).value()
555 }
556 (false, true, false, false) => {
557 -work_context.pi::<B>(reborrow_cache(&mut cache)).value()
558 }
559 _ => unreachable!(),
560 };
561 return Ok(res.with_precision(self.precision));
562 }
563
564 let y_f = FBig::<R, B>::new(work_context.repr_round(y.clone()).value(), work_context);
565 let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
566
567 match x_f.cmp(&FBig::<R, B>::ZERO) {
568 Ordering::Greater => {
569 let res =
570 work_context.atan_with_reduction(&(y_f / x_f), reborrow_cache(&mut cache));
571 Ok(res.with_precision(self.precision))
572 }
573 Ordering::Less => {
574 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
575 let y_sign = y_f.sign();
576 let atan_yx =
577 work_context.atan_with_reduction(&(y_f / x_f), reborrow_cache(&mut cache));
578 let res = if y_sign == Sign::Positive {
579 atan_yx + pi
580 } else {
581 atan_yx - pi
582 };
583 Ok(res.with_precision(self.precision))
584 }
585 Ordering::Equal => {
586 let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
588 let half_pi: FBig<R, B> = pi / 2;
589 if y_f > FBig::<R, B>::ZERO {
590 Ok(half_pi.with_precision(self.precision))
591 } else {
592 let res = -half_pi;
593 Ok(res.with_precision(self.precision))
594 }
595 }
596 }
597 }
598}
599
600impl<R: Round, const B: Word> FBig<R, B> {
601 #[inline]
606 pub fn sin(&self) -> Self {
607 self.context.unwrap_fp(self.context.sin(&self.repr, None))
608 }
609
610 #[inline]
615 pub fn cos(&self) -> Self {
616 self.context.unwrap_fp(self.context.cos(&self.repr, None))
617 }
618
619 #[inline]
626 pub fn sin_cos(&self) -> (Self, Self) {
627 let (s, c) = self.context.sin_cos(&self.repr, None);
628 (self.context.unwrap_fp(s), self.context.unwrap_fp(c))
629 }
630
631 #[inline]
638 pub fn tan(&self) -> Self {
639 self.context.unwrap_fp(self.context.tan(&self.repr, None))
640 }
641
642 #[inline]
647 pub fn asin(&self) -> Self {
648 self.context.unwrap_fp(self.context.asin(&self.repr, None))
649 }
650
651 #[inline]
656 pub fn acos(&self) -> Self {
657 self.context.unwrap_fp(self.context.acos(&self.repr, None))
658 }
659
660 #[inline]
662 pub fn atan(&self) -> Self {
663 self.context.unwrap_fp(self.context.atan(&self.repr, None))
664 }
665
666 #[inline]
671 pub fn atan2(&self, x: &Self) -> Self {
672 self.context
673 .unwrap_fp(self.context.atan2(&self.repr, &x.repr, None))
674 }
675}
676
677impl<R: Round> Context<R> {
678 #[must_use]
690 pub fn pi<const B: Word>(&self, cache: Option<&mut ConstCache>) -> Rounded<FBig<R, B>> {
691 if let Some(c) = cache {
692 return c.pi::<B, R>(self.precision);
693 }
694
695 let mut fresh = ConstCache::new();
699 fresh.pi::<B, R>(self.precision)
700 }
701}
702
703impl<R: Round, const B: Word> FBig<R, B> {
704 #[inline]
706 #[must_use]
707 pub fn pi(precision: usize) -> Self {
708 Context::<R>::new(precision).pi(None).value()
709 }
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715 use crate::round::mode;
716 use crate::DBig;
717 use core::str::FromStr;
718
719 #[test]
720 fn test_atan_infinity_is_preserved() {
721 let ctx = Context::<mode::HalfEven>::new(53);
722 let r = ctx.atan::<2>(&Repr::<2>::infinity(), None).unwrap().value();
724 assert!(r.repr().sign() == Sign::Positive);
725 assert!(r > FBig::<mode::HalfEven>::ONE);
727 }
728
729 #[test]
733 fn test_trig_tiny_negative_no_panic() {
734 let ctx = Context::<mode::HalfAway>::new(30);
735 for &e in &[-1isize, -2, -10, -30] {
736 let x = Repr::<10>::new(IBig::from(-1), e);
738 let s = ctx.sin::<10>(&x, None).unwrap().value();
739 let c = ctx.cos::<10>(&x, None).unwrap().value();
740 let (ss, cc) = ctx.sin_cos::<10>(&x, None);
741 let ss = ss.unwrap().value();
742 let cc = cc.unwrap().value();
743 assert_eq!(s.sign(), Sign::Negative);
745 assert_eq!(c.sign(), Sign::Positive);
746 assert_eq!(ss.sign(), Sign::Negative);
747 assert_eq!(cc.sign(), Sign::Positive);
748 }
749 }
750
751 #[test]
755 fn test_sin_many_digit_rounding_no_panic() {
756 let x = DBig::from_str("-5.525474318981006776603409487767135633516667011547942409467e-3")
757 .unwrap();
758 let ctx = Context::<mode::HalfEven>::new(100);
759 let s = ctx.sin::<10>(x.repr(), None).unwrap().value();
760 assert_eq!(s.sign(), Sign::Negative);
762 }
763}