use core::f32::INFINITY;
use core::f32::consts::{PI, FRAC_PI_2, FRAC_PI_4};
use float::{flip_sign_nonnan};
use ieee754::Ieee754;
#[inline]
pub fn atan_raw(x: f32) -> f32 {
const N2: f32 = 0.273;
(FRAC_PI_4 + N2 - N2 * x.abs()) * x
}
#[inline]
pub fn atan(x: f32) -> f32 {
if x.abs() > 1.0 {
debug_assert!(!x.is_nan());
flip_sign_nonnan(x, FRAC_PI_2) - atan_raw(1./x)
} else {
atan_raw(x)
}
}
#[inline]
pub fn atan2(y: f32, x: f32) -> f32 {
if y.abs() < x.abs() {
debug_assert!(!x.is_nan() && !y.is_nan() && !(y / x).is_nan());
let bias = if x > 0.0 { 0.0 } else { PI };
flip_sign_nonnan(y, bias) + atan_raw(y / x)
} else if x == 0. {
if y == 0. {
let bias = if x.is_sign_positive() { 0.0 } else { PI };
flip_sign_nonnan(y, bias)
} else if y.is_nan() {
y
} else {
FRAC_PI_2.copy_sign(y)
}
} else if y.abs() == INFINITY && x.abs() == INFINITY {
flip_sign_nonnan(y, FRAC_PI_2 - flip_sign_nonnan(x, FRAC_PI_4))
} else {
flip_sign_nonnan(y, FRAC_PI_2) - atan_raw(x / y)
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck as qc;
use std::f32 as f;
use ieee754::Ieee754;
const TOL: f32 = 0.0038;
#[test]
fn atan_abs_err_qc() {
fn prop(x: f32) -> qc::TestResult {
let e = atan(x);
let t = x.atan();
let abs = (e - t).abs();
if x == 0.0 {
qc::TestResult::from_bool(e == 0.0)
} else {
qc::TestResult::from_bool(abs < TOL)
}
}
qc::quickcheck(prop as fn(f32) -> qc::TestResult)
}
const PREC: u32 = 1 << 20;
#[test]
fn atan_abs_err_exhaustive() {
for i in 0..PREC + 1 {
for j in -5..6 {
let x = (1.0 + i as f32 / PREC as f32) * 2f32.powi(j * 20);
let e = atan(x);
let t = x.atan();
let abs = (e - t).abs();
assert!((e == 0. && x == 0.) || abs < TOL,
"{:.8}: {:.8}, {:.8}. {:.4}", x, e, t, abs);
}
}
}
#[test]
fn atan_edge_cases() {
assert!(atan(f::NAN).is_nan());
assert_eq!(atan(f::NEG_INFINITY), -PI / 2.);
assert_eq!(atan(0.), 0.);
assert_eq!(atan(f::INFINITY), PI / 2.);
}
#[test]
fn atan_denormals() {
fn prop(x: u8, y: u16) -> bool {
let signif = ((x as u32) << 16) | (y as u32);
let mut x = f32::recompose_raw(false, 1, signif);
for _ in 0..23 {
assert!(x > 0.0);
let e = atan(x);
let t = x.atan();
let abs = (e - t).abs();
if abs >= TOL {
return false
}
x /= 2.0;
}
true
}
qc::quickcheck(prop as fn(u8, u16) -> bool)
}
#[test]
fn atan2_abs_err_qc() {
fn prop(y: f32, x: f32) -> qc::TestResult {
let e = atan2(y, x);
let t = y.atan2(x);
let abs = (e - t).abs();
qc::TestResult::from_bool(abs < 0.0038)
}
qc::quickcheck(prop as fn(f32, f32) -> qc::TestResult)
}
#[test]
fn atan2_edge_cases() {
let values = &[-2., -1., -0., 0., 1., 2., f::INFINITY, f::NEG_INFINITY, f::NAN];
for &x in values {
for &y in values {
let e = atan2(x, y);
let t = x.atan2(y);
assert_eq!(e.is_nan(), t.is_nan());
if !t.is_nan() {
assert!((e - t).abs() < TOL ||
(e - t - 2.0 * PI).abs() < TOL ||
(e - t + 2.0 * PI).abs() < TOL);
}
}
}
}
}