| 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 | |