use crate::{
error::{assert_limited_precision, FpError},
fbig::FBig,
math::{
cache::{reborrow_cache, ConstCache},
FpResult,
},
repr::{Context, Repr, Word},
round::{Round, Rounded},
};
use core::cmp::Ordering;
use core::convert::TryFrom;
use dashu_base::{AbsOrd, Approximation::Exact, RemEuclid, Sign};
use dashu_int::IBig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Quadrant {
First,
Second,
Third,
Fourth,
}
fn signed_zero_normal<R: Round, const B: Word>(
ctx: &Context<R>,
x: &Repr<B>,
) -> FpResult<FBig<R, B>> {
let zero = if x.is_neg_zero() {
Repr::neg_zero()
} else {
Repr::zero()
};
Ok(Exact(FBig::<R, B>::new(zero, *ctx)))
}
impl<R: Round> Context<R> {
fn compute_work_context_trig<const B: Word>(self, x: &Repr<B>) -> Self {
let x_mag = (x.exponent.saturating_add(x.digits_ub() as isize)).max(0) as usize;
let extra_guards = 50 + x_mag / 10;
let work_precision = self
.precision
.saturating_add(x_mag)
.saturating_add(extra_guards);
Self::new(work_precision)
}
fn reduce_to_quadrant<const B: Word>(
self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> (Self, FBig<R, B>, Quadrant) {
let work_context = self.compute_work_context_trig(x);
let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
let half_pi = &pi / 2;
let x_scaled: FBig<R, B> = &x_f / &half_pi;
let k_f = x_scaled.round();
let r = x_f - &k_f * half_pi;
let k = IBig::try_from(k_f).expect("k_f is an exact integer or signed zero");
let k_mod_4_big = k.rem_euclid(IBig::from(4));
let Ok(k_mod_4_int) = i8::try_from(k_mod_4_big) else {
unreachable!("k % 4 is always in [0, 3]");
};
let quadrant = match k_mod_4_int {
0 => Quadrant::First,
1 => Quadrant::Second,
2 => Quadrant::Third,
3 => Quadrant::Fourth,
_ => unreachable!(),
};
(work_context, r, quadrant)
}
pub fn sin<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Err(FpError::InfiniteInput);
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return signed_zero_normal(self, x);
}
let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
let res = match quadrant {
Quadrant::First => work_context.sin_internal(&r),
Quadrant::Second => work_context.cos_internal(&r),
Quadrant::Third => -work_context.sin_internal(&r),
Quadrant::Fourth => -work_context.cos_internal(&r),
};
Ok(res.with_precision(self.precision))
}
fn sin_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
if x.repr.significand.is_zero() {
return FBig::ZERO;
}
let x2 = x.sqr();
let mut sum = x.clone();
let mut term = x.clone();
let mut k = 1usize;
let threshold = sum.sub_ulp();
loop {
term *= &x2;
term /= (2 * k) * (2 * k + 1);
if term.abs_cmp(&threshold).is_le() {
break;
}
if k % 2 == 1 {
sum -= &term;
} else {
sum += &term;
}
k += 1;
}
sum
}
pub fn cos<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Err(FpError::InfiniteInput);
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return Ok(FBig::<R, B>::ONE.with_precision(self.precision));
}
let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
let res = match quadrant {
Quadrant::First => work_context.cos_internal(&r),
Quadrant::Second => -work_context.sin_internal(&r),
Quadrant::Third => -work_context.cos_internal(&r),
Quadrant::Fourth => work_context.sin_internal(&r),
};
Ok(res.with_precision(self.precision))
}
fn cos_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
if x.repr.significand.is_zero() {
return FBig::ONE.with_precision(self.precision).value();
}
let x2 = x.sqr();
let mut sum = FBig::<R, B>::ONE.with_precision(self.precision).value();
let mut term = sum.clone();
let mut k = 1usize;
let threshold = sum.sub_ulp();
loop {
term *= &x2;
term /= (2 * k) * (2 * k - 1);
if term.abs_cmp(&threshold).is_le() {
break;
}
if k % 2 == 1 {
sum -= &term;
} else {
sum += &term;
}
k += 1;
}
sum
}
pub fn sin_cos<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> (FpResult<FBig<R, B>>, FpResult<FBig<R, B>>) {
if x.is_infinite() {
return (Err(FpError::InfiniteInput), Err(FpError::InfiniteInput));
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
let s = signed_zero_normal(self, x);
let c = Ok(FBig::<R, B>::ONE.with_precision(self.precision));
return (s, c);
}
let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
let (sin_r, cos_r) = work_context.sin_cos_internal(&r);
let (s, c) = match quadrant {
Quadrant::First => (sin_r, cos_r),
Quadrant::Second => (cos_r, -sin_r),
Quadrant::Third => (-sin_r, -cos_r),
Quadrant::Fourth => (-cos_r, sin_r),
};
(Ok(s.with_precision(self.precision)), Ok(c.with_precision(self.precision)))
}
pub(crate) fn sin_cos_internal<const B: Word>(
self,
x: &FBig<R, B>,
) -> (FBig<R, B>, FBig<R, B>) {
if x.repr.significand.is_zero() {
return (FBig::ZERO, FBig::ONE.with_precision(self.precision).value());
}
let x2 = x.sqr();
let mut sin_sum = x.clone();
let mut cos_sum = FBig::<R, B>::ONE.with_precision(self.precision).value();
let mut sin_term = x.clone();
let mut cos_term = cos_sum.clone();
let mut k = 1usize;
let sin_threshold = sin_sum.sub_ulp();
let cos_threshold = cos_sum.sub_ulp();
loop {
cos_term *= &x2;
cos_term /= (2 * k) * (2 * k - 1);
sin_term *= &x2;
sin_term /= (2 * k) * (2 * k + 1);
if sin_term.abs_cmp(&sin_threshold).is_le() && cos_term.abs_cmp(&cos_threshold).is_le()
{
break;
}
if k % 2 == 1 {
cos_sum -= &cos_term;
sin_sum -= &sin_term;
} else {
cos_sum += &cos_term;
sin_sum += &sin_term;
}
k += 1;
}
(sin_sum, cos_sum)
}
pub fn tan<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Err(FpError::InfiniteInput);
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return signed_zero_normal(self, x);
}
let (work_context, r, quadrant) = self.reduce_to_quadrant(x, reborrow_cache(&mut cache));
let (sin_r, cos_r) = work_context.sin_cos_internal(&r);
let (s_f, c_f) = match quadrant {
Quadrant::First => (sin_r, cos_r),
Quadrant::Second => (cos_r, -sin_r),
Quadrant::Third => (-sin_r, -cos_r),
Quadrant::Fourth => (-cos_r, sin_r),
};
if c_f.repr.is_pos_zero() {
let inf = if s_f.sign() == Sign::Negative {
Repr::neg_infinity()
} else {
Repr::infinity()
};
return Ok(Rounded::Exact(FBig::new(inf, *self)));
}
self.div(&s_f.repr, &c_f.repr)
.map(|r| r.and_then(|f| f.with_precision(self.precision)))
}
pub fn asin<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Err(FpError::InfiniteInput);
}
assert_limited_precision(self.precision);
let x_orig = FBig::<R, B>::new(x.clone(), *self);
if x_orig.abs_cmp(&FBig::ONE).is_gt() {
return Err(FpError::OutOfDomain);
}
let guard_digits = 50;
let work_precision = self.precision + guard_digits;
let work_context = Self::new(work_precision);
let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
let res = work_context.asin_internal(&x_f, reborrow_cache(&mut cache));
Ok(res.with_precision(self.precision))
}
fn asin_internal<const B: Word>(
self,
x_f: &FBig<R, B>,
mut cache: Option<&mut ConstCache>,
) -> FBig<R, B> {
let one = FBig::<R, B>::ONE.with_precision(self.precision).value();
let x2 = x_f.sqr();
let d = self.unwrap_fp(self.sqrt(&(one - x2).repr));
if d.repr.is_pos_zero() || d.repr.is_neg_zero() {
let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
let half_pi: FBig<R, B> = pi / 2;
if x_f.sign() == Sign::Positive {
return half_pi;
}
return -half_pi;
}
self.atan_with_reduction(&(x_f / d), reborrow_cache(&mut cache))
}
pub fn acos<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Err(FpError::InfiniteInput);
}
assert_limited_precision(self.precision);
let x_orig = FBig::<R, B>::new(x.clone(), *self);
if x_orig.abs_cmp(&FBig::ONE).is_gt() {
return Err(FpError::OutOfDomain);
}
let guard_digits = 50;
let work_precision = self.precision + guard_digits;
let work_context = Self::new(work_precision);
let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
let asin_x = work_context.asin_internal(&x_f, reborrow_cache(&mut cache));
let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
let half_pi: FBig<R, B> = pi / 2;
let res: FBig<R, B> = half_pi - asin_x;
Ok(res.with_precision(self.precision))
}
pub fn atan<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
let half_pi: FBig<R, B> = pi / 2;
let res: FBig<R, B> = if x.sign() == Sign::Positive {
half_pi
} else {
-half_pi
};
return Ok(res.with_precision(self.precision));
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return signed_zero_normal(self, x);
}
let guard_digits = 50;
let work_precision = self.precision + guard_digits;
let work_context = Self::new(work_precision);
let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
let res = work_context.atan_with_reduction(&x_f, reborrow_cache(&mut cache));
Ok(res.with_precision(self.precision))
}
fn atan_with_reduction<const B: Word>(
self,
x_f: &FBig<R, B>,
mut cache: Option<&mut ConstCache>,
) -> FBig<R, B> {
let sign = x_f.sign();
let mut x_abs = x_f.clone();
if sign == Sign::Negative {
x_abs = -x_abs;
}
let mut res = if x_abs >= FBig::<R, B>::ONE.with_precision(self.precision).value() {
let pi = self.pi::<B>(reborrow_cache(&mut cache)).value();
let inv_x = FBig::<R, B>::ONE.with_precision(self.precision).value() / x_abs;
(pi / 2) - self.atan_internal(&inv_x)
} else {
self.atan_internal(&x_abs)
};
if sign == Sign::Negative {
res = -res;
}
res
}
fn atan_internal<const B: Word>(self, x: &FBig<R, B>) -> FBig<R, B> {
let x2 = x.sqr();
let one_plus_x2 = FBig::ONE + &x2;
let mut term = x / &one_plus_x2;
let mut sum = term.clone();
let factor = (2 * &x2) / one_plus_x2;
let mut n = 1usize;
let threshold = sum.sub_ulp();
loop {
term *= &factor;
term *= n;
term /= 2 * n + 1;
if term.abs_cmp(&threshold).is_le() {
break;
}
sum += &term;
n += 1;
}
sum
}
pub fn atan2<const B: Word>(
&self,
y: &Repr<B>,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if y.is_finite() && x.is_finite() && y.significand.is_zero() && x.significand.is_zero() {
return Err(FpError::OutOfDomain);
}
assert_limited_precision(self.precision);
let guard_digits = 50;
let work_precision = self.precision + guard_digits;
let work_context = Self::new(work_precision);
if y.is_infinite() || x.is_infinite() {
let (sy, sx) = (y.sign() == Sign::Positive, x.sign() == Sign::Positive);
let res: FBig<R, B> = match (y.is_infinite(), x.is_infinite(), sy, sx) {
(true, true, true, true) => {
work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 4
}
(true, true, true, false) => {
work_context.pi::<B>(reborrow_cache(&mut cache)).value() * 3 / 4
}
(true, true, false, true) => {
let pi4: FBig<R, B> =
work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 4;
-pi4
}
(true, true, false, false) => {
let pi34: FBig<R, B> =
work_context.pi::<B>(reborrow_cache(&mut cache)).value() * 3 / 4;
-pi34
}
(true, false, true, _) => {
work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 2
}
(true, false, false, _) => {
let half_pi: FBig<R, B> =
work_context.pi::<B>(reborrow_cache(&mut cache)).value() / 2;
-half_pi
}
(false, true, _, true) => {
if sy {
FBig::<R, B>::ZERO.with_precision(work_precision).value()
} else {
FBig::<R, B>::new(Repr::neg_zero(), work_context)
.with_precision(work_precision)
.value()
}
}
(false, true, true, false) => {
work_context.pi::<B>(reborrow_cache(&mut cache)).value()
}
(false, true, false, false) => {
-work_context.pi::<B>(reborrow_cache(&mut cache)).value()
}
_ => unreachable!(),
};
return Ok(res.with_precision(self.precision));
}
let y_f = FBig::<R, B>::new(work_context.repr_round(y.clone()).value(), work_context);
let x_f = FBig::<R, B>::new(work_context.repr_round(x.clone()).value(), work_context);
match x_f.cmp(&FBig::<R, B>::ZERO) {
Ordering::Greater => {
let res =
work_context.atan_with_reduction(&(y_f / x_f), reborrow_cache(&mut cache));
Ok(res.with_precision(self.precision))
}
Ordering::Less => {
let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
let y_sign = y_f.sign();
let atan_yx =
work_context.atan_with_reduction(&(y_f / x_f), reborrow_cache(&mut cache));
let res = if y_sign == Sign::Positive {
atan_yx + pi
} else {
atan_yx - pi
};
Ok(res.with_precision(self.precision))
}
Ordering::Equal => {
let pi = work_context.pi::<B>(reborrow_cache(&mut cache)).value();
let half_pi: FBig<R, B> = pi / 2;
if y_f > FBig::<R, B>::ZERO {
Ok(half_pi.with_precision(self.precision))
} else {
let res = -half_pi;
Ok(res.with_precision(self.precision))
}
}
}
}
}
impl<R: Round, const B: Word> FBig<R, B> {
#[inline]
pub fn sin(&self) -> Self {
self.context.unwrap_fp(self.context.sin(&self.repr, None))
}
#[inline]
pub fn cos(&self) -> Self {
self.context.unwrap_fp(self.context.cos(&self.repr, None))
}
#[inline]
pub fn sin_cos(&self) -> (Self, Self) {
let (s, c) = self.context.sin_cos(&self.repr, None);
(self.context.unwrap_fp(s), self.context.unwrap_fp(c))
}
#[inline]
pub fn tan(&self) -> Self {
self.context.unwrap_fp(self.context.tan(&self.repr, None))
}
#[inline]
pub fn asin(&self) -> Self {
self.context.unwrap_fp(self.context.asin(&self.repr, None))
}
#[inline]
pub fn acos(&self) -> Self {
self.context.unwrap_fp(self.context.acos(&self.repr, None))
}
#[inline]
pub fn atan(&self) -> Self {
self.context.unwrap_fp(self.context.atan(&self.repr, None))
}
#[inline]
pub fn atan2(&self, x: &Self) -> Self {
self.context
.unwrap_fp(self.context.atan2(&self.repr, &x.repr, None))
}
}
impl<R: Round> Context<R> {
#[must_use]
pub fn pi<const B: Word>(&self, cache: Option<&mut ConstCache>) -> Rounded<FBig<R, B>> {
if let Some(c) = cache {
return c.pi::<B, R>(self.precision);
}
let mut fresh = ConstCache::new();
fresh.pi::<B, R>(self.precision)
}
}
impl<R: Round, const B: Word> FBig<R, B> {
#[inline]
#[must_use]
pub fn pi(precision: usize) -> Self {
Context::<R>::new(precision).pi(None).value()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::round::mode;
use crate::DBig;
use core::str::FromStr;
#[test]
fn test_atan_infinity_is_preserved() {
let ctx = Context::<mode::HalfEven>::new(53);
let r = ctx.atan::<2>(&Repr::<2>::infinity(), None).unwrap().value();
assert!(r.repr().sign() == Sign::Positive);
assert!(r > FBig::<mode::HalfEven>::ONE);
}
#[test]
fn test_trig_tiny_negative_no_panic() {
let ctx = Context::<mode::HalfAway>::new(30);
for &e in &[-1isize, -2, -10, -30] {
let x = Repr::<10>::new(IBig::from(-1), e);
let s = ctx.sin::<10>(&x, None).unwrap().value();
let c = ctx.cos::<10>(&x, None).unwrap().value();
let (ss, cc) = ctx.sin_cos::<10>(&x, None);
let ss = ss.unwrap().value();
let cc = cc.unwrap().value();
assert_eq!(s.sign(), Sign::Negative);
assert_eq!(c.sign(), Sign::Positive);
assert_eq!(ss.sign(), Sign::Negative);
assert_eq!(cc.sign(), Sign::Positive);
}
}
#[test]
fn test_sin_many_digit_rounding_no_panic() {
let x = DBig::from_str("-5.525474318981006776603409487767135633516667011547942409467e-3")
.unwrap();
let ctx = Context::<mode::HalfEven>::new(100);
let s = ctx.sin::<10>(x.repr(), None).unwrap().value();
assert_eq!(s.sign(), Sign::Negative);
}
}