1//! arctangent approximation for a single-precision float.
2//!
3//! Method described at:
4//! <https://ieeexplore.ieee.org/document/6375931>
5
6use super::F32;
7use core::f32::consts::FRAC_PI_2;
8
9impl F32 {
10 /// Approximates `atan(x)` approximation in radians with a maximum error of
11 /// `0.002`.
12 ///
13 /// Returns [`Self::NAN`] if the number is [`Self::NAN`].
14 pub fn atan(self) -> Self {
15 FRAC_PI_2 * self.atan_norm()
16 }
17
18 /// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum
19 /// error of `0.1620` degrees.
20 pub fn atan_norm(self) -> Self {
21 const SIGN_MASK: u32 = 0x8000_0000;
22 const B: f32 = 0.596_227;
23
24 // Extract the sign bit
25 let ux_s = SIGN_MASK & self.to_bits();
26
27 // Calculate the arctangent in the first quadrant
28 let bx_a = (B * self).abs();
29 let n = bx_a + self * self;
30 let atan_1q = n / (1.0 + bx_a + n);
31
32 // Restore the sign bit and convert to float
33 Self::from_bits(ux_s | atan_1q.to_bits())
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::F32;
40 use core::f32::consts;
41
42 /// 0.1620 degrees in radians
43 const MAX_ERROR: f32 = 0.003;
44
45 #[test]
46 fn sanity_check() {
47 // Arctangent test vectors - `(input, output)`
48 let test_vectors: &[(f32, f32)] = &[
49 (3.0_f32.sqrt() / 3.0, consts::FRAC_PI_6),
50 (1.0, consts::FRAC_PI_4),
51 (3.0_f32.sqrt(), consts::FRAC_PI_3),
52 (-(3.0_f32.sqrt()) / 3.0, -consts::FRAC_PI_6),
53 (-1.0, -consts::FRAC_PI_4),
54 (-(3.0_f32.sqrt()), -consts::FRAC_PI_3),
55 ];
56
57 for &(x, expected) in test_vectors {
58 let actual = F32(x).atan().0;
59 let delta = actual - expected;
60
61 assert!(
62 delta <= MAX_ERROR,
63 "delta {} too large: {} vs {}",
64 delta,
65 actual,
66 expected
67 );
68 }
69 }
70
71 #[test]
72 fn zero() {
73 assert_eq!(F32::ZERO.atan(), F32::ZERO);
74 }
75
76 #[test]
77 fn nan() {
78 assert!(F32::NAN.atan().is_nan());
79 }
80}
81