use super::consts::{FRAC_2_PI, FRAC_3PI_4, FRAC_PI_2, FRAC_PI_2_TAIL, FRAC_PI_4, INV_FACT, PI};
use super::{DoubleDouble as Dd, two_prod};
const MAX_REDUCIBLE: f64 = 1_125_899_906_842_624.0;
const ATAN2_FAST_MIN: f64 = f64::from_bits((1023 - 450) << 52);
const ATAN2_FAST_MAX: f64 = f64::from_bits((1023 + 450) << 52);
const ATAN2_TINY_RATIO: f64 = f64::from_bits((1023 - 968) << 52);
#[inline]
fn reduce_pio2(x: Dd) -> Option<(i64, Dd)> {
let a = x.hi.abs();
if a <= FRAC_PI_4.hi() {
return Some((0, x));
}
if a < MAX_REDUCIBLE || within_reduction_limit(x) {
let k = (x.hi * FRAC_2_PI.hi()).round();
let (a, b) = two_prod(k, FRAC_PI_2.hi());
let (c, d) = two_prod(k, FRAC_PI_2.lo());
let r = x
- Dd::from_parts_unchecked(a, b)
- Dd::from_parts_unchecked(c, d)
- k * FRAC_PI_2_TAIL;
return Some((k as i64, r));
}
None
}
#[cold]
#[inline(never)]
fn within_reduction_limit(x: Dd) -> bool {
x.hi.abs() == MAX_REDUCIBLE && x.lo * x.hi <= 0.0
}
fn sin_taylor(r: Dd) -> Dd {
if r.hi == 0.0 {
return r; }
let r2 = r * r;
let mut p = INV_FACT[29];
for k in (3..=27).rev().step_by(2) {
p = INV_FACT[k] - r2 * p;
}
r - r * r2 * p
}
fn cos_taylor(r: Dd) -> Dd {
let r2 = r * r;
let mut p = INV_FACT[28];
for k in (2..=26).rev().step_by(2) {
p = INV_FACT[k] - r2 * p;
}
Dd::ONE - r2 * p
}
fn special_atan2(y: f64, x: f64) -> Dd {
let a = y.atan2(x);
let exact = [FRAC_PI_4, FRAC_PI_2, FRAC_3PI_4, PI]
.into_iter()
.find(|c| c.hi() == a.abs());
match exact {
Some(c) if a < 0.0 => -c,
Some(c) => c,
None => Dd::from_f64(a), }
}
#[inline]
fn signed_as(limit: Dd, y: f64) -> Dd {
if y.is_sign_negative() { -limit } else { limit }
}
#[inline]
fn atan2_newton(y: Dd, x: Dd) -> Dd {
atan2_from(y, x, y.hi.atan2(x.hi))
}
#[inline]
fn atan2_from(y: Dd, x: Dd, z: f64) -> Dd {
let (s, c) = Dd::from_f64(z).sin_cos();
let t = (y * c - x * s) / (x * c + y * s);
let r = t + z;
let limit = if x.hi > 0.0 { FRAC_PI_2 } else { PI };
if r.abs() > limit {
return signed_as(limit, y.hi);
}
r
}
fn exponent(v: f64) -> i32 {
let bits = |v: f64| ((v.to_bits() >> 52) & 0x7ff) as i32 - 1023;
if v.abs() < f64::MIN_POSITIVE {
bits(v * f64::from_bits((1023 + 64) << 52)) - 64
} else {
bits(v)
}
}
#[cold]
#[inline(never)]
fn atan2_wide(y: Dd, x: Dd) -> Dd {
if y.hi.is_nan() || x.hi.is_nan() {
return Dd::NAN;
}
if y.hi == 0.0 || x.hi == 0.0 || y.hi.is_infinite() || x.hi.is_infinite() {
return special_atan2(y.hi, x.hi);
}
let e = exponent(y.hi.abs().max(x.hi.abs()));
let (y, x) = (y.ldexp(-e), x.ldexp(-e));
if y.hi.abs().min(x.hi.abs()) >= ATAN2_TINY_RATIO {
return atan2_newton(y, x);
}
if y.hi.abs() < x.hi.abs() {
if x.hi > 0.0 {
return y / x; }
return signed_as(PI, y.hi);
}
signed_as(FRAC_PI_2, y.hi)
}
#[inline]
fn sqrt_one_minus_square(a: Dd) -> Dd {
((Dd::ONE - a) * (Dd::ONE + a)).sqrt()
}
impl Dd {
pub fn sin(self) -> Self {
let Some((k, r)) = reduce_pio2(self) else {
return Self::NAN;
};
match k.rem_euclid(4) {
0 => sin_taylor(r),
1 => cos_taylor(r),
2 => -sin_taylor(r),
_ => -cos_taylor(r),
}
}
pub fn cos(self) -> Self {
let Some((k, r)) = reduce_pio2(self) else {
return Self::NAN;
};
match k.rem_euclid(4) {
0 => cos_taylor(r),
1 => -sin_taylor(r),
2 => -cos_taylor(r),
_ => sin_taylor(r),
}
}
pub fn sin_cos(self) -> (Self, Self) {
let Some((k, r)) = reduce_pio2(self) else {
return (Self::NAN, Self::NAN);
};
let (s, c) = (sin_taylor(r), cos_taylor(r));
match k.rem_euclid(4) {
0 => (s, c),
1 => (c, -s),
2 => (-s, -c),
_ => (-c, s),
}
}
pub fn tan(self) -> Self {
let (s, c) = self.sin_cos();
s / c
}
pub fn atan2(self, x: Self) -> Self {
let direct = |v: f64| (ATAN2_FAST_MIN..=ATAN2_FAST_MAX).contains(&v.abs());
if direct(self.hi) && direct(x.hi) {
return atan2_newton(self, x);
}
atan2_wide(self, x)
}
pub fn atan(self) -> Self {
self.atan2(Self::ONE)
}
pub fn asin(self) -> Self {
let a = self.abs();
if self.hi.is_nan() || a > Self::ONE {
return Self::NAN;
}
self.atan2(sqrt_one_minus_square(a))
}
pub fn acos(self) -> Self {
let a = self.abs();
if self.hi.is_nan() || a > Self::ONE {
return Self::NAN;
}
sqrt_one_minus_square(a).atan2(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn two_pow(e: i32) -> f64 {
f64::from_bits(((e + 1023) as u64) << 52)
}
#[test]
fn newton_step_tolerates_inaccurate_starting_values() {
let d = Dd::from_f64;
let cases = [
(d(1.0), d(1.0)),
(d(1e-3), d(1.0)),
(d(1.0), d(1e-3)),
(d(-0.3), d(-2.0)),
(d(2.5), d(-1e-20)),
(d(1e-30), d(-1.0)),
(d(-1e-30), d(-1.0)),
(d(1.0), d(1e-30)),
(d(-1.0), d(1e-30)),
(Dd::new(0.7, 1e-17), Dd::new(-0.2, 3e-18)),
(Dd::new(1e-200, -3e-217), Dd::new(4e-201, 1e-217)),
];
for (y, x) in cases {
let want = y.atan2(x);
let limit = if x.hi > 0.0 { FRAC_PI_2 } else { PI };
let z0 = y.hi.atan2(x.hi);
for ulps in [-1024.0, -16.0, -1.0, 1.0, 16.0, 1024.0] {
let z = z0 * (1.0 + ulps * f64::EPSILON);
let got = atan2_from(y, x, z);
let err = ((got - want) / want).abs().hi;
assert!(
err <= two_pow(-101),
"atan2({y:?}, {x:?}) from {z}: {got:?}"
);
assert!(got.abs() <= limit, "atan2({y:?}, {x:?}) from {z}: {got:?}");
}
}
}
#[test]
fn newton_step_never_passes_the_range_limits() {
let d = Dd::from_f64;
for s in [1.0, -1.0] {
for (y, x, limit) in [(d(s * 1e-30), d(-1.0), PI), (d(s), d(1e-30), FRAC_PI_2)] {
let z = y.hi.atan2(x.hi) * (1.0 - two_pow(-20));
let unclamped = {
let (sz, cz) = Dd::from_f64(z).sin_cos();
(y * cz - x * sz) / (x * cz + y * sz) + z
};
assert!(unclamped.abs() > limit, "{unclamped:?}");
assert_eq!(atan2_from(y, x, z), signed_as(limit, s));
}
}
}
}