1 | //! Four quadrant arctangent approximation for a single-precision float. |
2 | //! |
3 | //! Method described at: <https://ieeexplore.ieee.org/document/6375931> |
4 | |
5 | use super::F32; |
6 | use core::f32::consts::PI; |
7 | |
8 | impl F32 { |
9 | /// Approximates the four quadrant arctangent of `self` (`y`) and |
10 | /// `rhs` (`x`) in radians with a maximum error of `0.002`. |
11 | /// |
12 | /// - `x = 0`, `y = 0`: `0` |
13 | /// - `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]` |
14 | /// - `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]` |
15 | /// - `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)` |
16 | pub fn atan2(self, rhs: Self) -> Self { |
17 | let n = self.atan2_norm(rhs); |
18 | PI / 2.0 * if n > 2.0 { n - 4.0 } else { n } |
19 | } |
20 | |
21 | /// Approximates `atan2(y,x)` normalized to the `[0, 4)` range with a maximum |
22 | /// error of `0.1620` degrees. |
23 | pub(crate) fn atan2_norm(self, rhs: Self) -> Self { |
24 | const SIGN_MASK: u32 = 0x8000_0000; |
25 | const B: f32 = 0.596_227; |
26 | |
27 | let y = self; |
28 | let x = rhs; |
29 | |
30 | // Extract sign bits from floating point values |
31 | let ux_s = SIGN_MASK & x.to_bits(); |
32 | let uy_s = SIGN_MASK & y.to_bits(); |
33 | |
34 | // Determine quadrant offset |
35 | let q = ((!ux_s & uy_s) >> 29 | ux_s >> 30) as f32; |
36 | |
37 | // Calculate arctangent in the first quadrant |
38 | let bxy_a = (B * x * y).abs(); |
39 | let n = bxy_a + y * y; |
40 | let atan_1q = n / (x * x + bxy_a + n); |
41 | |
42 | // Translate it to the proper quadrant |
43 | let uatan_2q = (ux_s ^ uy_s) | atan_1q.to_bits(); |
44 | Self(q) + Self::from_bits(uatan_2q) |
45 | } |
46 | } |
47 | |
48 | #[cfg (test)] |
49 | mod tests { |
50 | use super::F32; |
51 | use core::f32::consts::PI; |
52 | |
53 | /// 0.1620 degrees in radians |
54 | const MAX_ERROR: f32 = 0.003; |
55 | |
56 | #[test ] |
57 | fn sanity_check() { |
58 | let test_vectors: &[(f32, f32, f32)] = &[ |
59 | (0.0, 1.0, 0.0), |
60 | (0.0, -1.0, PI), |
61 | (3.0, 2.0, (3.0f32 / 2.0).atan()), |
62 | (2.0, -1.0, (2.0f32 / -1.0).atan() + PI), |
63 | (-2.0, -1.0, (-2.0f32 / -1.0).atan() - PI), |
64 | ]; |
65 | |
66 | for &(y, x, expected) in test_vectors { |
67 | let actual = F32(y).atan2(F32(x)).0; |
68 | let delta = actual - expected; |
69 | |
70 | assert!( |
71 | delta <= MAX_ERROR, |
72 | "delta {} too large: {} vs {}" , |
73 | delta, |
74 | actual, |
75 | expected |
76 | ); |
77 | } |
78 | } |
79 | } |
80 | |