1 | //! arccos approximation for a single-precision float. |
2 | //! |
3 | //! Method described at: |
4 | //! <https://math.stackexchange.com/questions/2908908/express-arccos-in-terms-of-arctan> |
5 | |
6 | use super::F32; |
7 | use core::f32::consts::PI; |
8 | |
9 | impl F32 { |
10 | /// Computes `acos(x)` approximation in radians in the range `[0, pi]`. |
11 | pub(crate) fn acos(self) -> Self { |
12 | if self > 0.0 { |
13 | ((Self::ONE - self * self).sqrt() / self).atan() |
14 | } else if self == 0.0 { |
15 | Self(PI / 2.0) |
16 | } else { |
17 | ((Self::ONE - self * self).sqrt() / self).atan() + PI |
18 | } |
19 | } |
20 | } |
21 | |
22 | #[cfg (test)] |
23 | mod tests { |
24 | use super::F32; |
25 | use core::f32::consts; |
26 | |
27 | const MAX_ERROR: f32 = 0.03; |
28 | |
29 | #[test ] |
30 | fn sanity_check() { |
31 | // Arccosine test vectors - `(input, output)` |
32 | let test_vectors: &[(f32, f32)] = &[ |
33 | (2.000, f32::NAN), |
34 | (1.000, 0.0), |
35 | (0.866, consts::FRAC_PI_6), |
36 | (0.707, consts::FRAC_PI_4), |
37 | (0.500, consts::FRAC_PI_3), |
38 | (f32::EPSILON, consts::FRAC_PI_2), |
39 | (0.000, consts::FRAC_PI_2), |
40 | (-f32::EPSILON, consts::FRAC_PI_2), |
41 | (-0.500, 2.0 * consts::FRAC_PI_3), |
42 | (-0.707, 3.0 * consts::FRAC_PI_4), |
43 | (-0.866, 5.0 * consts::FRAC_PI_6), |
44 | (-1.000, consts::PI), |
45 | (-2.000, f32::NAN), |
46 | ]; |
47 | |
48 | for &(x, expected) in test_vectors { |
49 | let actual = F32(x).acos(); |
50 | if expected.is_nan() { |
51 | assert!( |
52 | actual.is_nan(), |
53 | "acos({}) returned {}, should be NAN" , |
54 | x, |
55 | actual |
56 | ); |
57 | } else { |
58 | let delta = (actual - expected).abs(); |
59 | |
60 | assert!( |
61 | delta <= MAX_ERROR, |
62 | "delta {} too large: {} vs {}" , |
63 | delta, |
64 | actual, |
65 | expected |
66 | ); |
67 | } |
68 | } |
69 | } |
70 | } |
71 | |