fast_math/
atan.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use core::f32::INFINITY;
use core::f32::consts::{PI, FRAC_PI_2, FRAC_PI_4};
use float::{flip_sign_nonnan};
use ieee754::Ieee754;

/// Compute a fast approximation of the inverse tangent for `|x| < 1`.
///
/// This will return unspecified nonsense if `x` is doesn't not
/// satisfy those constraints. Use `atan2` if correct handling is
/// required (at the expense of some speed).
#[inline]
pub fn atan_raw(x: f32) -> f32 {
    // Quadratic approximation recommended in
    // http://www-labs.iro.umontreal.ca/~mignotte/IFT2425/Documents/EfficientApproximationArctgFunction.pdf.
    const N2: f32 = 0.273;
    (FRAC_PI_4 + N2 - N2 * x.abs()) * x
}

/// Compute a fast approximation of the arctangent of `x`.
///
/// The maximum absolute error across all f32s is less than 0.0038.
///
/// See also `atan_raw` which only works on `|x| <= 1`, but is faster.
#[inline]
pub fn atan(x: f32) -> f32 {
    if x.abs() > 1.0 {
        // if x is NaN, abs(x) is NaN, so the comparison can't succeed
        debug_assert!(!x.is_nan());
        flip_sign_nonnan(x, FRAC_PI_2) - atan_raw(1./x)
    } else {
        atan_raw(x)
    }
}

/// Compute a fast approximation of the four quadrant arctangent of `y` and `x`.
///
/// The maximum absolute error across all f32s is less than 0.0038.
#[inline]
pub fn atan2(y: f32, x: f32) -> f32 {
    if y.abs() < x.abs() {
        // x is not NaN and y is finite, so there should be no NaNs
        // around
        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. {
        // x is non-NaN
        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 {
        // x and y are both infinite, meaning: not NaN, can't be
        // divided, and the answer is statically obvious (some
        // multiple of PI/4).
        flip_sign_nonnan(y, FRAC_PI_2 - flip_sign_nonnan(x, FRAC_PI_4))
    } else {
        // Either one x or y is NaN (propogates through atan_raw
        // properly), or |y| >= |x| (meaning |r| = |y / x| >= 1). Use
        // `atan(1/r) == sign(r) * pi / 2 - atan(r)`, but inline the 0
        // or PI `x` bias.
        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;

    /// Maximal absolute error according to paper.
    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);
                }
            }
        }
    }
}