Skip to main content

core_math_rs/f16/
atan.rs

1/* Correctly-rounded arc-tangent for binary16 value.
2
3Copyright (c) 2025 Paul Zimmermann
4
5This file is ported from the CORE-MATH project
6(https://core-math.gitlabpages.inria.fr/).
7
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14
15The above copyright notice and this permission notice shall be included in all
16copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
25*/
26
27use hexf::hexf32 as h;
28
29// the following polynomials were generated using Sollya (cf atan.sollya)
30
31/* Degree-7 minimax polynomial for atan(x) over [0,0.25], with relative error
32   bounded by 2^-25.419, with coefficients of odd degree only, and degree-1
33   coefficient forced to 1. Coefficients were later optimized to reduce the
34   number of exceptions.
35*/
36const P0: [f32; 4] = [
37    h!("0x1.fffffcp-1"),
38    h!("-0x1.55546cp-2"),
39    h!("0x1.98d0ep-3"),
40    h!("-0x1.0c7c54p-3"),
41];
42
43/* degree-4 minimax polynomial for atan(x) over [0.25,0.5], with relative error
44bounded by 2^-22.573 */
45const P1: [f32; 5] = [
46    h!("0x1.411612p-14"),
47    h!("0x1.ff076cp-1"),
48    h!("0x1.1ee64cp-6"),
49    h!("-0x1.a6fc96p-2"),
50    h!("0x1.81dd3ep-3"),
51];
52
53/* degree-4 minimax polynomial for atan(x) over [0.5,0.75], with relative error
54bounded by 2^-21.757 */
55const P2: [f32; 5] = [
56    h!("-0x1.95964cp-8"),
57    h!("0x1.0bad76p+0"),
58    h!("-0x1.e652e2p-4"),
59    h!("-0x1.e70ab4p-3"),
60    h!("0x1.a5eb88p-4"),
61];
62
63/* degree-4 minimax polynomial for atan(x) over [0.75,1], with relative error
64bounded by 2^-23.027 */
65const P3: [f32; 5] = [
66    h!("-0x1.f75b16p-6"),
67    h!("0x1.2d3d1p+0"),
68    h!("-0x1.882376p-2"),
69    h!("0x1.d18c96p-13"),
70    h!("0x1.6aa268p-6"),
71];
72
73/// Correctly-rounded arc-tangent for binary16 value.
74pub fn cr_atanf16(x: f16) -> f16 {
75    let v = f32::from(x);
76    let u = v.to_bits();
77    let au = u & 0x7fffffff;
78
79    const HALF_PI: f32 = h!("0x1.921fb6p+0");
80
81    if au >= 0x7f800000 {
82        // NaN or Inf
83        if au == 0x7f800000 {
84            // +/-Inf
85            return if u == 0x7f800000 { HALF_PI } else { -HALF_PI } as f16;
86        }
87        return x + x; // will signal invalid for sNaN and return qNaN
88    }
89
90    let mut t = v;
91
92    // for x < 0 we use atan(-x) = -atan(x)
93    let neg = (u >> 31) != 0;
94    const NEG: [f32; 2] = [1.0, -1.0];
95    let s = NEG[usize::from(neg)];
96    t *= s;
97
98    // now t >= 0
99
100    // for x > 1 we use atan(x) = pi/2 - atan(1/x)
101    let reduce = au > 0x3f800000;
102    if reduce {
103        t = 1.0 / t;
104    }
105
106    // now 0 <= t <= 1
107
108    let tt = t * t;
109    let mut y;
110    if t <= 0.25 {
111        // for |x| < 0x1.d14p-6, atan(x) rounds to x to nearest
112        if !reduce && (t <= h!("0x1.d14p-6")) {
113            if au == 0 {
114                return x; // x = 0
115            }
116            t *= s;
117            return f32::mul_add(t, h!("-0x1p-23"), t) as f16;
118        }
119
120        // deal with exceptional cases
121        if au == 0x3e56a000 {
122            // |x| = 0x1.ad4p-3
123            return if au == u {
124                h!("0x1.a72002p-3")
125            } else {
126                h!("-0x1.a72002p-3")
127            } as f16;
128        }
129        if au == 0x4115c000 {
130            // |x| = 0x1.2b8p+3
131            return if au == u {
132                h!("0x1.76dffep+0")
133            } else {
134                h!("-0x1.76dffep+0")
135            } as f16;
136        }
137        if au == 0x42c32000 {
138            // |x| = 0x1.864p+6
139            return if au == u {
140                h!("0x1.8f7ffep+0")
141            } else {
142                h!("-0x1.8f7ffep+0")
143            } as f16;
144        }
145
146        let p = P0;
147        let c5 = f32::mul_add(p[3], tt, p[2]);
148        let c1 = f32::mul_add(p[1], tt, p[0]);
149        let c1 = f32::mul_add(c5, tt * tt, c1);
150        y = t * c1;
151    } else {
152        let p = if t <= 0.5 {
153            P1
154        } else if t <= 0.75 {
155            P2
156        } else {
157            P3
158        };
159        let c3 = f32::mul_add(p[4], t, p[3]);
160        let c2 = f32::mul_add(c3, t, p[2]);
161        y = f32::mul_add(p[1], t, p[0]);
162        y = f32::mul_add(c2, tt, y);
163    }
164
165    if reduce {
166        y = HALF_PI - y; // argument reconstruction
167    }
168
169    if neg {
170        y = -y;
171    }
172
173    y as f16
174}
175
176#[cfg(test)]
177mod tests {
178    #[test]
179    fn exhaustive() {
180        for b in 0..=u16::MAX {
181            let x = f16::from_bits(b);
182            let y1 = super::cr_atanf16(x);
183            let y2 = core_math::atanf16(x);
184            assert_eq!(
185                y1.to_bits(),
186                y2.to_bits(),
187                "atanf16({x} @ {b:#04x}) = ({y1} @ {y1b:#04x}) vs ({y2} @ {y2b:#04x})",
188                y1b = y1.to_bits(),
189                y2b = y2.to_bits()
190            );
191        }
192    }
193
194    #[test]
195    fn edge() {
196        assert_eq!(
197            super::cr_atanf16(-f16::NAN).to_bits(),
198            (-f16::NAN).to_bits()
199        );
200        assert_eq!(
201            super::cr_atanf16(-f16::INFINITY),
202            -std::f16::consts::FRAC_PI_2,
203        );
204        assert_eq!(super::cr_atanf16(-0.0).to_bits(), (-0.0_f16).to_bits());
205        assert_eq!(super::cr_atanf16(0.0).to_bits(), (0.0_f16).to_bits());
206        assert_eq!(
207            super::cr_atanf16(f16::INFINITY),
208            std::f16::consts::FRAC_PI_2
209        );
210        assert_eq!(super::cr_atanf16(f16::NAN).to_bits(), (f16::NAN).to_bits());
211    }
212}