Skip to main content

core_math_rs/f16/
acos.rs

1/* Correctly-rounded arc-cosine 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
27#![expect(clippy::approx_constant)]
28
29use hexf::hexf32 as h;
30
31use super::utils::snanf16;
32
33// the following polynomials were generated using Sollya (cf acos.sollya)
34
35/* degree-4 minimax polynomial for acos(x) over [0,0.25], with relative error
36   bounded by 2^-22.943, manually optimized to reduce the number of exceptions
37*/
38const P0: [f32; 5] = [
39    h!("0x1.921fb4p0"),
40    h!("-0x1.fffb44p-1"),
41    h!("-0x1.25e6b8p-10"),
42    h!("-0x1.3cc114p-3"),
43    h!("-0x1.a85b22p-5"),
44];
45
46/* degree-4 minimax polynomial for acos(x) over [0.25,0.5], with relative error
47   bounded by 2^-20.789, manually optimized to reduce the number of exceptions
48*/
49const P1: [f32; 5] = [
50    h!("0x1.91b678p0"),
51    h!("-0x1.f515cap-1"),
52    h!("-0x1.bd043ap-4"),
53    h!("0x1.7e2d5ap-4"),
54    h!("-0x1.190806p-2"),
55];
56
57/* degree-4 minimax polynomial for acos(x)/sqrt(1-x) over [0.5,1],
58with relative error bounded by 2^-23.583 */
59const P2: [f32; 5] = [
60    h!("0x1.91fa1cp0"),
61    h!("-0x1.ae5c5ep-3"),
62    h!("0x1.31640cp-4"),
63    h!("-0x1.98038p-6"),
64    h!("0x1.251b5p-8"),
65];
66
67/// Correctly-rounded arc-cosine for binary16 value.
68pub fn cr_acosf16(x: f16) -> f16 {
69    let v = f32::from(x);
70    let u = v.to_bits();
71    let au = u & 0x7fffffff;
72
73    if au >= 0x3f800000 {
74        // NaN, Inf, or |x| >= 1
75        if au == 0x3f800000 {
76            if u == 0x3f800000 {
77                return 0.0;
78            }
79            return h!("0x1.921fb6p+1") as f16; // TODO
80        }
81
82        if (au >> 23) == 0x3ff && ((au & 0x7fffff) != 0) {
83            // qNaN or sNaN
84            return x;
85        }
86        return snanf16(); // will signal invalid and return sNaN
87    }
88
89    let mut t = v;
90    let tt = t * t;
91
92    if (u >> 31) != 0 {
93        // x < 0
94        t = -t;
95    }
96
97    let c1;
98    let c3;
99    let mut y;
100    if au < 0x3e800000 {
101        // |x| < 0.25
102        c1 = f32::mul_add(P0[2], t, P0[1]);
103        c3 = f32::mul_add(P0[4], t, P0[3]);
104        y = f32::mul_add(c3, tt, c1);
105        y = f32::mul_add(y, t, P0[0]);
106        if (u >> 31) != 0
107        // x < 0
108        {
109            y = h!("0x1.921fb4p+1") - y;
110            /* below we deal with a few exceptions that we were unable to remove
111            by tuning the coefficients of p0 */
112            if (0x36960000..=0x369a0000).contains(&au) {
113                y = h!("0x1.922002p+0"); // x = -0x1.2cp-18 or -0x1.3p-18 or -0x1.34p-18
114            }
115            if u == 0xbcf80000 {
116                y = h!("0x1.99e002p+0"); // x = -0x1.fp-6
117            }
118            if u == 0xbcfc0000 {
119                y = h!("0x1.9a0006p+0"); // -0x1.f8p-6
120            }
121        }
122    } else if au < 0x3f000000 {
123        // 0.25 <= |x| < 0.5
124        c1 = f32::mul_add(P1[2], t, P1[1]);
125        c3 = f32::mul_add(P1[4], t, P1[3]);
126        y = f32::mul_add(c3, tt, c1);
127        y = f32::mul_add(y, t, P1[0]);
128        if (u >> 31) != 0 {
129            // x < 0
130            y = h!("0x1.921fb4p+1") - y;
131        }
132    } else {
133        // 0.5 <= |x| <= 1
134        c1 = f32::mul_add(P2[2], t, P2[1]);
135        c3 = f32::mul_add(P2[4], t, P2[3]);
136        y = f32::mul_add(c3, tt, c1);
137        y = f32::mul_add(y, t, P2[0]);
138        y *= (1.0 - t).sqrt();
139        if (u >> 31) != 0 {
140            // x < 0
141            y = h!("0x1.921fb4p+1") - y;
142        }
143    }
144
145    y as f16
146}
147
148#[cfg(test)]
149mod tests {
150    #[test]
151    fn exhaustive() {
152        for b in 0..=u16::MAX {
153            let x = f16::from_bits(b);
154            let y1 = super::cr_acosf16(x);
155            let y2 = core_math::acosf16(x);
156            assert_eq!(
157                y1.to_bits(),
158                y2.to_bits(),
159                "acosf16({x} @ {b:#04x}) = ({y1} @ {y1b:#04x}) vs ({y2} @ {y2b:#04x})",
160                y1b = y1.to_bits(),
161                y2b = y2.to_bits()
162            );
163        }
164    }
165
166    #[test]
167    fn edge() {
168        assert_eq!(
169            super::cr_acosf16(-f16::NAN).to_bits(),
170            super::snanf16().to_bits() // FIXME: sign
171        );
172        assert_eq!(
173            super::cr_acosf16(-f16::INFINITY).to_bits(),
174            super::snanf16().to_bits() // FIXME: sign
175        );
176        assert_eq!(super::cr_acosf16(-1.0), std::f16::consts::PI);
177        assert_eq!(super::cr_acosf16(-0.0), std::f16::consts::FRAC_PI_2);
178        assert_eq!(super::cr_acosf16(0.0), std::f16::consts::FRAC_PI_2);
179        assert_eq!(super::cr_acosf16(1.0).to_bits(), (0.0_f16).to_bits());
180        assert_eq!(
181            super::cr_acosf16(f16::INFINITY).to_bits(),
182            super::snanf16().to_bits() // FIXME: sign
183        );
184        assert_eq!(
185            super::cr_acosf16(f16::NAN).to_bits(),
186            super::snanf16().to_bits() // FIXME: sign
187        );
188    }
189}