#![allow(clippy::indexing_slicing)]
#![allow(clippy::excessive_precision)]
#![allow(clippy::approx_constant)]
#![allow(clippy::eq_op)]
use super::atan;
use crate::Real;
const PI: Real = 3.1415926535897931160E+00;
const PI_LO: Real = 1.2246467991473531772E-16;
pub const fn atan2(y: Real, x: Real) -> Real {
if x.is_nan() || y.is_nan() {
return x + y;
}
let mut ix = (Real::to_bits(x) >> 32) as u32;
let lx = Real::to_bits(x) as u32;
let mut iy = (Real::to_bits(y) >> 32) as u32;
let ly = Real::to_bits(y) as u32;
if ((ix.wrapping_sub(0x3ff00000)) | lx) == 0 {
return atan(y);
}
let m = ((iy >> 31) & 1) | ((ix >> 30) & 2);
ix &= 0x7fffffff;
iy &= 0x7fffffff;
if (iy | ly) == 0 {
return match m {
0 | 1 => y,
2 => PI,
_ => -PI,
};
}
if (ix | lx) == 0 {
return if m & 1 != 0 { -PI / 2.0 } else { PI / 2.0 };
}
if ix == 0x7ff00000 {
if iy == 0x7ff00000 {
return match m {
0 => PI / 4.0,
1 => -PI / 4.0,
2 => 3.0 * PI / 4.0,
_ => -3.0 * PI / 4.0,
};
} else {
return match m {
0 => 0.0,
1 => -0.0,
2 => PI,
_ => -PI,
};
}
}
if ix.wrapping_add(64 << 20) < iy || iy == 0x7ff00000 {
return if m & 1 != 0 { -PI / 2.0 } else { PI / 2.0 };
}
let z = if (m & 2 != 0) && iy.wrapping_add(64 << 20) < ix {
0.0
} else {
atan(y.abs() / x.abs())
};
match m {
0 => z,
1 => -z,
2 => PI - (z - PI_LO),
_ => (z - PI_LO) - PI,
}
}
#[cfg(all(test, feature = "std"))]
mod atan2_tests {
use super::atan2;
use crate::Real;
const PI: Real = 3.1415926535897931160E+00;
const PI_LO: Real = 1.2246467991473531772E-16;
const PI_2: Real = 1.5707963267948965580E+00;
const PI_4: Real = 0.78539816339744830962E+00;
const THREE_PI_4: Real = 2.3561944901923449288E+00;
fn assert_close(a: Real, b: Real, msg: &str) {
if a.is_nan() && b.is_nan() {
return;
}
let diff = (a - b).abs();
if diff == 0.0 {
return;
}
let ulps = if a == 0.0 || b == 0.0 {
diff.to_bits() as i64
} else {
(a.to_bits() as i64).wrapping_sub(b.to_bits() as i64).abs()
};
assert!(
ulps <= 1 || diff / b.abs() < 1e-15,
"{msg}\n expected: {b:.20e}\n got: {a:.20e}\n ulps: {ulps}"
);
}
#[test]
fn sanity_check() {
let cases = [
(1.0, 1.0, PI_4),
(Real::sqrt(3.0), 1.0, PI / 3.0),
(1.0, Real::sqrt(3.0), PI / 6.0),
(0.0, 1.0, 0.0),
(1.0, 0.0, PI_2),
(-1.0, 1.0, -PI_4),
(-Real::sqrt(3.0), 1.0, -PI / 3.0),
(-1.0, Real::sqrt(3.0), -PI / 6.0),
(0.0, -1.0, PI),
(1.0, -1.0, THREE_PI_4),
(Real::sqrt(3.0), -1.0, 2.0 * PI / 3.0),
(-1.0, -1.0, -THREE_PI_4),
(-Real::sqrt(3.0), -1.0, -2.0 * PI / 3.0),
(-1.0, 0.0, -PI_2),
];
for (y, x, expected) in cases.iter() {
assert_close(atan2(*y, *x), *expected, &format!("atan2({y}, {x})"));
}
}
#[test]
fn special_values() {
assert!(atan2(Real::NAN, 1.0).is_nan());
assert!(atan2(1.0, Real::NAN).is_nan());
assert!(atan2(Real::NAN, Real::NAN).is_nan());
assert_eq!(atan2(0.0, 1.0), 0.0);
assert_eq!(atan2(-0.0, 1.0), -0.0);
assert_eq!(atan2(0.0, -1.0), PI);
assert_eq!(atan2(-0.0, -1.0), -PI);
assert_eq!(atan2(1.0, 0.0), PI_2);
assert_eq!(atan2(-1.0, 0.0), -PI_2);
assert_eq!(atan2(1.0, Real::INFINITY), 0.0);
assert_eq!(atan2(-1.0, Real::INFINITY), -0.0);
assert_eq!(atan2(1.0, Real::NEG_INFINITY), PI);
assert_eq!(atan2(-1.0, Real::NEG_INFINITY), -PI);
assert_eq!(atan2(Real::INFINITY, 1.0), PI_2);
assert_eq!(atan2(Real::NEG_INFINITY, 1.0), -PI_2);
assert_eq!(atan2(Real::INFINITY, Real::INFINITY), PI_4);
assert_eq!(atan2(Real::NEG_INFINITY, Real::INFINITY), -PI_4);
assert_eq!(atan2(Real::INFINITY, Real::NEG_INFINITY), THREE_PI_4);
assert_eq!(atan2(Real::NEG_INFINITY, Real::NEG_INFINITY), -THREE_PI_4);
}
#[test]
fn extreme_ratio_cases() {
let huge = Real::from_bits(0x7fe0_0000_0000_0000);
assert_eq!(atan2(huge, 1.0), PI_2);
assert_eq!(atan2(-huge, 1.0), -PI_2);
assert_eq!(atan2(huge, -1.0), PI_2);
assert_eq!(atan2(-huge, -1.0), -PI_2);
let tiny = Real::from_bits(0x0010_0000_0000_0000);
assert_eq!(atan2(tiny, -1.0), PI); assert_eq!(atan2(-tiny, -1.0), -PI);
assert_eq!(atan2(tiny, 1.0), tiny); }
#[test]
fn pi_lo_correction() {
let y = 10.0;
let x = -1.0;
let result = atan2(y, x);
let z = super::atan(y.abs() / x.abs());
let expected = PI - (z - PI_LO);
assert_close(result, expected, "PI_LO correction (x < 0, y > 0)");
}
#[test]
fn fast_path_x_equals_one() {
let y = 0.5;
assert_close(atan2(y, 1.0), super::atan(y), "fast-path x == 1.0");
}
}