Skip to main content

c/
math_invtrig.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// Translated from musl: asin.c, acos.c, atan.c, atan2.c,
5// asinf.c, acosf.c, atanf.c, atan2f.c
6
7// ============================================================
8// Double-precision: shared constants for asin/acos
9// ============================================================
10
11const INVTRIG_PIO2_HI: f64 = asdouble(0x3FF921FB54442D18);
12const INVTRIG_PIO2_LO: f64 = asdouble(0x3C91A62633145C07);
13
14const INVTRIG_PS0: f64 = asdouble(0x3FC5555555555555);
15const INVTRIG_PS1: f64 = asdouble(0xBFD4D61203EB6F7D);
16const INVTRIG_PS2: f64 = asdouble(0x3FC9C1550E884455);
17const INVTRIG_PS3: f64 = asdouble(0xBFA48228B5688F3B);
18const INVTRIG_PS4: f64 = asdouble(0x3F49EFE07501B288);
19const INVTRIG_PS5: f64 = asdouble(0x3F023DE10DFDF709);
20const INVTRIG_QS1: f64 = asdouble(0xC0033A271C8A2D4B);
21const INVTRIG_QS2: f64 = asdouble(0x40002AE59C598AC8);
22const INVTRIG_QS3: f64 = asdouble(0xBFE6066C1B8D0159);
23const INVTRIG_QS4: f64 = asdouble(0x3FB3B8C5B12E9282);
24
25// Rational approximation R(z) shared by asin and acos.
26#[inline]
27fn invtrig_r(z: f64) -> f64 {
28    let p = z * (INVTRIG_PS0 + z * (INVTRIG_PS1 + z * (INVTRIG_PS2
29        + z * (INVTRIG_PS3 + z * (INVTRIG_PS4 + z * INVTRIG_PS5)))));
30    let q = 1.0 + z * (INVTRIG_QS1 + z * (INVTRIG_QS2 + z * (INVTRIG_QS3 + z * INVTRIG_QS4)));
31    p / q
32}
33
34// ============================================================
35// asin (double)
36// ============================================================
37
38#[no_mangle]
39pub unsafe extern "C" fn asin(x: f64) -> f64 {
40    let hx = get_high_word(x);
41    let ix = hx & 0x7fffffff;
42    // |x| >= 1 or nan
43    if ix >= 0x3ff00000 {
44        let lx = get_low_word(x);
45        if (ix.wrapping_sub(0x3ff00000) | lx) == 0 {
46            // asin(1) = +-pi/2 with inexact
47            return x * INVTRIG_PIO2_HI + asdouble(0x3870000000000000); // 0x1p-120f
48        }
49        return 0.0 / (x - x);
50    }
51    // |x| < 0.5
52    if ix < 0x3fe00000 {
53        // if 0x1p-1022 <= |x| < 0x1p-26, avoid raising underflow
54        if ix < 0x3e500000 && ix >= 0x00100000 {
55            return x;
56        }
57        return x + x * invtrig_r(x * x);
58    }
59    // 1 > |x| >= 0.5
60    let z = (1.0 - fabs(x)) * 0.5;
61    let s = sqrt(z);
62    let r = invtrig_r(z);
63    let result;
64    if ix >= 0x3fef3333 {
65        // |x| > 0.975
66        result = INVTRIG_PIO2_HI - (2.0 * (s + s * r) - INVTRIG_PIO2_LO);
67    } else {
68        // f+c = sqrt(z)
69        let mut f = s;
70        set_low_word(&mut f, 0);
71        let c = (z - f * f) / (s + f);
72        result = 0.5 * INVTRIG_PIO2_HI
73            - (2.0 * s * r - (INVTRIG_PIO2_LO - 2.0 * c) - (0.5 * INVTRIG_PIO2_HI - 2.0 * f));
74    }
75    if (hx >> 31) != 0 {
76        return -result;
77    }
78    result
79}
80
81// ============================================================
82// acos (double)
83// ============================================================
84
85#[no_mangle]
86pub unsafe extern "C" fn acos(x: f64) -> f64 {
87    let hx = get_high_word(x);
88    let ix = hx & 0x7fffffff;
89    // |x| >= 1 or nan
90    if ix >= 0x3ff00000 {
91        let lx = get_low_word(x);
92        if (ix.wrapping_sub(0x3ff00000) | lx) == 0 {
93            // acos(1)=0, acos(-1)=pi
94            if (hx >> 31) != 0 {
95                return 2.0 * INVTRIG_PIO2_HI + asdouble(0x3870000000000000); // 0x1p-120f
96            }
97            return 0.0;
98        }
99        return 0.0 / (x - x);
100    }
101    // |x| < 0.5
102    if ix < 0x3fe00000 {
103        if ix <= 0x3c600000 {
104            // |x| < 2**-57
105            return INVTRIG_PIO2_HI + asdouble(0x3870000000000000); // 0x1p-120f
106        }
107        return INVTRIG_PIO2_HI - (x - (INVTRIG_PIO2_LO - x * invtrig_r(x * x)));
108    }
109    // x < -0.5
110    if (hx >> 31) != 0 {
111        let z = (1.0 + x) * 0.5;
112        let s = sqrt(z);
113        let w = invtrig_r(z) * s - INVTRIG_PIO2_LO;
114        return 2.0 * (INVTRIG_PIO2_HI - (s + w));
115    }
116    // x > 0.5
117    let z = (1.0 - x) * 0.5;
118    let s = sqrt(z);
119    let mut df = s;
120    set_low_word(&mut df, 0);
121    let c = (z - df * df) / (s + df);
122    let w = invtrig_r(z) * s + c;
123    2.0 * (df + w)
124}
125
126// ============================================================
127// atan (double)
128// ============================================================
129
130const INVTRIG_ATANHI: [f64; 4] = [
131    asdouble(0x3FDDAC670561BB4F), // atan(0.5)hi
132    asdouble(0x3FE921FB54442D18), // atan(1.0)hi
133    asdouble(0x3FEF730BD281F69B), // atan(1.5)hi
134    asdouble(0x3FF921FB54442D18), // atan(inf)hi
135];
136
137const INVTRIG_ATANLO: [f64; 4] = [
138    asdouble(0x3C7A2B7F222F65E2), // atan(0.5)lo
139    asdouble(0x3C81A62633145C07), // atan(1.0)lo
140    asdouble(0x3C7007887AF0CBBD), // atan(1.5)lo
141    asdouble(0x3C91A62633145C07), // atan(inf)lo
142];
143
144const INVTRIG_AT: [f64; 11] = [
145    asdouble(0x3FD555555555550D),
146    asdouble(0xBFC999999998EBC4),
147    asdouble(0x3FC24924920083FF),
148    asdouble(0xBFBC71C6FE231671),
149    asdouble(0x3FB745CDC54C206E),
150    asdouble(0xBFB3B0F2AF749A6D),
151    asdouble(0x3FB10D66A0D03D51),
152    asdouble(0xBFADDE2D52DEFD9A),
153    asdouble(0x3FA97B4B24760DEB),
154    asdouble(0xBFA2B4442C6A6C2F),
155    asdouble(0x3F90AD3AE322DA11),
156];
157
158#[no_mangle]
159pub unsafe extern "C" fn atan(x: f64) -> f64 {
160    let ix_raw = get_high_word(x);
161    let sign = ix_raw >> 31;
162    let mut ix = ix_raw & 0x7fffffff;
163    let mut x = x;
164
165    if ix >= 0x44100000 {
166        // |x| >= 2^66
167        if is_nan(x) {
168            return x;
169        }
170        let z = INVTRIG_ATANHI[3] + asdouble(0x3870000000000000); // 0x1p-120f
171        return if sign != 0 { -z } else { z };
172    }
173
174    let id;
175    if ix < 0x3fdc0000 {
176        // |x| < 0.4375
177        if ix < 0x3e400000 {
178            // |x| < 2^-27
179            if ix < 0x00100000 {
180                // raise underflow for subnormal x
181                force_eval(x as f32);
182            }
183            return x;
184        }
185        id = -1i32;
186    } else {
187        x = fabs(x);
188        ix = get_high_word(x) & 0x7fffffff;
189        if ix < 0x3ff30000 {
190            // |x| < 1.1875
191            if ix < 0x3fe60000 {
192                // 7/16 <= |x| < 11/16
193                id = 0;
194                x = (2.0 * x - 1.0) / (2.0 + x);
195            } else {
196                // 11/16 <= |x| < 19/16
197                id = 1;
198                x = (x - 1.0) / (x + 1.0);
199            }
200        } else {
201            if ix < 0x40038000 {
202                // |x| < 2.4375
203                id = 2;
204                x = (x - 1.5) / (1.0 + 1.5 * x);
205            } else {
206                // 2.4375 <= |x| < 2^66
207                id = 3;
208                x = -1.0 / x;
209            }
210        }
211    }
212
213    // end of argument reduction
214    let z = x * x;
215    let w = z * z;
216    // break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly
217    let s1 = z * (INVTRIG_AT[0] + w * (INVTRIG_AT[2] + w * (INVTRIG_AT[4]
218        + w * (INVTRIG_AT[6] + w * (INVTRIG_AT[8] + w * INVTRIG_AT[10])))));
219    let s2 = w * (INVTRIG_AT[1] + w * (INVTRIG_AT[3] + w * (INVTRIG_AT[5]
220        + w * (INVTRIG_AT[7] + w * INVTRIG_AT[9]))));
221    if id < 0 {
222        return x - x * (s1 + s2);
223    }
224    let z = INVTRIG_ATANHI[id as usize] - (x * (s1 + s2) - INVTRIG_ATANLO[id as usize] - x);
225    if sign != 0 { -z } else { z }
226}
227
228// ============================================================
229// atan2 (double)
230// ============================================================
231
232const INVTRIG_PI: f64 = asdouble(0x400921FB54442D18);
233const INVTRIG_PI_LO: f64 = asdouble(0x3CA1A62633145C07);
234
235#[no_mangle]
236pub unsafe extern "C" fn atan2(y: f64, x: f64) -> f64 {
237    if is_nan(x) || is_nan(y) {
238        return x + y;
239    }
240    let mut ix: u32 = 0;
241    let mut lx: u32 = 0;
242    let mut iy: u32 = 0;
243    let mut ly: u32 = 0;
244    extract_words(&mut ix, &mut lx, x);
245    extract_words(&mut iy, &mut ly, y);
246
247    if (ix.wrapping_sub(0x3ff00000) | lx) == 0 {
248        // x = 1.0
249        return atan(y);
250    }
251    let m = ((iy >> 31) & 1) | ((ix >> 30) & 2); // 2*sign(x)+sign(y)
252    ix &= 0x7fffffff;
253    iy &= 0x7fffffff;
254
255    // when y = 0
256    if (iy | ly) == 0 {
257        match m {
258            0 | 1 => return y,              // atan(+-0,+anything)=+-0
259            2 => return INVTRIG_PI,          // atan(+0,-anything) = pi
260            3 => return -INVTRIG_PI,         // atan(-0,-anything) =-pi
261            _ => core::hint::unreachable_unchecked(),
262        }
263    }
264    // when x = 0
265    if (ix | lx) == 0 {
266        return if (m & 1) != 0 { -INVTRIG_PI / 2.0 } else { INVTRIG_PI / 2.0 };
267    }
268    // when x is INF
269    if ix == 0x7ff00000 {
270        if iy == 0x7ff00000 {
271            match m {
272                0 => return INVTRIG_PI / 4.0,           // atan(+INF,+INF)
273                1 => return -INVTRIG_PI / 4.0,          // atan(-INF,+INF)
274                2 => return 3.0 * INVTRIG_PI / 4.0,     // atan(+INF,-INF)
275                3 => return -3.0 * INVTRIG_PI / 4.0,    // atan(-INF,-INF)
276                _ => core::hint::unreachable_unchecked(),
277            }
278        } else {
279            match m {
280                0 => return 0.0,                // atan(+...,+INF)
281                1 => return -0.0,               // atan(-...,+INF)
282                2 => return INVTRIG_PI,          // atan(+...,-INF)
283                3 => return -INVTRIG_PI,         // atan(-...,-INF)
284                _ => core::hint::unreachable_unchecked(),
285            }
286        }
287    }
288    // |y/x| > 0x1p64
289    if ix.wrapping_add(64 << 20) < iy || iy == 0x7ff00000 {
290        return if (m & 1) != 0 { -INVTRIG_PI / 2.0 } else { INVTRIG_PI / 2.0 };
291    }
292
293    // z = atan(|y/x|) without spurious underflow
294    let z;
295    if (m & 2) != 0 && iy.wrapping_add(64 << 20) < ix {
296        // |y/x| < 0x1p-64, x<0
297        z = 0.0;
298    } else {
299        z = atan(fabs(y / x));
300    }
301    match m {
302        0 => z,                              // atan(+,+)
303        1 => -z,                             // atan(-,+)
304        2 => INVTRIG_PI - (z - INVTRIG_PI_LO), // atan(+,-)
305        _ => (z - INVTRIG_PI_LO) - INVTRIG_PI, // atan(-,-)
306    }
307}
308
309// ============================================================
310// Single-precision: shared constants for asinf/acosf
311// ============================================================
312
313const INVTRIG_PS0_F: f32 = 1.6666586697e-01;
314const INVTRIG_PS1_F: f32 = -4.2743422091e-02;
315const INVTRIG_PS2_F: f32 = -8.6563630030e-03;
316const INVTRIG_QS1_F: f32 = -7.0662963390e-01;
317
318// Rational approximation R(z) for single-precision asinf/acosf.
319#[inline]
320fn invtrig_rf(z: f32) -> f32 {
321    let p = z * (INVTRIG_PS0_F + z * (INVTRIG_PS1_F + z * INVTRIG_PS2_F));
322    let q = 1.0f32 + z * INVTRIG_QS1_F;
323    p / q
324}
325
326// ============================================================
327// asinf (float)
328// ============================================================
329
330const INVTRIG_PIO2_F: f64 = asdouble(0x3FF921FB54442D18);
331
332#[no_mangle]
333pub unsafe extern "C" fn asinf(x: f32) -> f32 {
334    let hx = get_float_word(x);
335    let ix = hx & 0x7fffffff;
336    // |x| >= 1
337    if ix >= 0x3f800000 {
338        if ix == 0x3f800000 {
339            // |x| == 1
340            return (x as f64 * INVTRIG_PIO2_F + asdouble(0x3870000000000000)) as f32; // 0x1p-120f
341        }
342        return 0.0f32 / (x - x); // asin(|x|>1) is NaN
343    }
344    // |x| < 0.5
345    if ix < 0x3f000000 {
346        // if 0x1p-126 <= |x| < 0x1p-12, avoid raising underflow
347        if ix < 0x39800000 && ix >= 0x00800000 {
348            return x;
349        }
350        return x + x * invtrig_rf(x * x);
351    }
352    // 1 > |x| >= 0.5
353    let z = (1.0f32 - fabsf(x)) * 0.5f32;
354    let s = sqrt(z as f64);
355    let result = INVTRIG_PIO2_F - 2.0 * (s + s * invtrig_rf(z) as f64);
356    let result = result as f32;
357    if (hx >> 31) != 0 {
358        return -result;
359    }
360    result
361}
362
363// ============================================================
364// acosf (float)
365// ============================================================
366
367const INVTRIG_PIO2_HI_F: f32 = asfloat(0x3fc90fda);
368const INVTRIG_PIO2_LO_F: f32 = asfloat(0x33a22168);
369
370#[no_mangle]
371pub unsafe extern "C" fn acosf(x: f32) -> f32 {
372    let hx = get_float_word(x);
373    let ix = hx & 0x7fffffff;
374    // |x| >= 1 or nan
375    if ix >= 0x3f800000 {
376        if ix == 0x3f800000 {
377            if (hx >> 31) != 0 {
378                return 2.0 * INVTRIG_PIO2_HI_F + asfloat(0x03800000); // 0x1p-120f
379            }
380            return 0.0;
381        }
382        return 0.0f32 / (x - x);
383    }
384    // |x| < 0.5
385    if ix < 0x3f000000 {
386        if ix <= 0x32800000 {
387            // |x| < 2**-26
388            return INVTRIG_PIO2_HI_F + asfloat(0x03800000); // 0x1p-120f
389        }
390        return INVTRIG_PIO2_HI_F - (x - (INVTRIG_PIO2_LO_F - x * invtrig_rf(x * x)));
391    }
392    // x < -0.5
393    if (hx >> 31) != 0 {
394        let z = (1.0f32 + x) * 0.5f32;
395        let s = sqrtf(z);
396        let w = invtrig_rf(z) * s - INVTRIG_PIO2_LO_F;
397        return 2.0 * (INVTRIG_PIO2_HI_F - (s + w));
398    }
399    // x > 0.5
400    let z = (1.0f32 - x) * 0.5f32;
401    let s = sqrtf(z);
402    let hz = get_float_word(s);
403    let df = asfloat(hz & 0xfffff000);
404    let c = (z - df * df) / (s + df);
405    let w = invtrig_rf(z) * s + c;
406    2.0 * (df + w)
407}
408
409// ============================================================
410// atanf (float)
411// ============================================================
412
413const INVTRIG_ATANHI_F: [f32; 4] = [
414    asfloat(0x3eed6338), // atan(0.5)hi
415    asfloat(0x3f490fda), // atan(1.0)hi
416    asfloat(0x3f7b985e), // atan(1.5)hi
417    asfloat(0x3fc90fda), // atan(inf)hi
418];
419
420const INVTRIG_ATANLO_F: [f32; 4] = [
421    asfloat(0x31ac3769), // atan(0.5)lo
422    asfloat(0x33222168), // atan(1.0)lo
423    asfloat(0x33140fb4), // atan(1.5)lo
424    asfloat(0x33a22168), // atan(inf)lo
425];
426
427const INVTRIG_AT_F: [f32; 5] = [
428    3.3333328366e-01,
429    -1.9999158382e-01,
430    1.4253635705e-01,
431    -1.0648017377e-01,
432    6.1687607318e-02,
433];
434
435#[no_mangle]
436pub unsafe extern "C" fn atanf(x: f32) -> f32 {
437    let ix_raw = get_float_word(x);
438    let sign = ix_raw >> 31;
439    let mut ix = ix_raw & 0x7fffffff;
440    let mut x = x;
441
442    if ix >= 0x4c800000 {
443        // |x| >= 2**26
444        if is_nanf(x) {
445            return x;
446        }
447        let z = INVTRIG_ATANHI_F[3] + asfloat(0x03800000); // 0x1p-120f
448        return if sign != 0 { -z } else { z };
449    }
450
451    let id;
452    if ix < 0x3ee00000 {
453        // |x| < 0.4375
454        if ix < 0x39800000 {
455            // |x| < 2**-12
456            if ix < 0x00800000 {
457                // raise underflow for subnormal x
458                force_eval(x * x);
459            }
460            return x;
461        }
462        id = -1i32;
463    } else {
464        x = fabsf(x);
465        ix = get_float_word(x) & 0x7fffffff;
466        if ix < 0x3f980000 {
467            // |x| < 1.1875
468            if ix < 0x3f300000 {
469                // 7/16 <= |x| < 11/16
470                id = 0;
471                x = (2.0f32 * x - 1.0f32) / (2.0f32 + x);
472            } else {
473                // 11/16 <= |x| < 19/16
474                id = 1;
475                x = (x - 1.0f32) / (x + 1.0f32);
476            }
477        } else {
478            if ix < 0x401c0000 {
479                // |x| < 2.4375
480                id = 2;
481                x = (x - 1.5f32) / (1.0f32 + 1.5f32 * x);
482            } else {
483                // 2.4375 <= |x| < 2**26
484                id = 3;
485                x = -1.0f32 / x;
486            }
487        }
488    }
489
490    // end of argument reduction
491    let z = x * x;
492    let w = z * z;
493    let s1 = z * (INVTRIG_AT_F[0] + w * (INVTRIG_AT_F[2] + w * INVTRIG_AT_F[4]));
494    let s2 = w * (INVTRIG_AT_F[1] + w * INVTRIG_AT_F[3]);
495    if id < 0 {
496        return x - x * (s1 + s2);
497    }
498    let z = INVTRIG_ATANHI_F[id as usize] - ((x * (s1 + s2) - INVTRIG_ATANLO_F[id as usize]) - x);
499    if sign != 0 { -z } else { z }
500}
501
502// ============================================================
503// atan2f (float)
504// ============================================================
505
506const INVTRIG_PI_F: f32 = asfloat(0x40490fdb);
507const INVTRIG_PI_LO_F: f32 = asfloat(0xb3bbbd2e);
508
509#[no_mangle]
510pub unsafe extern "C" fn atan2f(y: f32, x: f32) -> f32 {
511    if is_nanf(x) || is_nanf(y) {
512        return x + y;
513    }
514    let ix: u32;
515    let iy: u32;
516    ix = get_float_word(x);
517    iy = get_float_word(y);
518
519    if ix == 0x3f800000 {
520        // x=1.0
521        return atanf(y);
522    }
523    let m = ((iy >> 31) & 1) | ((ix >> 30) & 2); // 2*sign(x)+sign(y)
524    let ix = ix & 0x7fffffff;
525    let iy = iy & 0x7fffffff;
526
527    // when y = 0
528    if iy == 0 {
529        match m {
530            0 | 1 => return y,              // atan(+-0,+anything)=+-0
531            2 => return INVTRIG_PI_F,        // atan(+0,-anything) = pi
532            3 => return -INVTRIG_PI_F,       // atan(-0,-anything) =-pi
533            _ => core::hint::unreachable_unchecked(),
534        }
535    }
536    // when x = 0
537    if ix == 0 {
538        return if (m & 1) != 0 { -INVTRIG_PI_F / 2.0 } else { INVTRIG_PI_F / 2.0 };
539    }
540    // when x is INF
541    if ix == 0x7f800000 {
542        if iy == 0x7f800000 {
543            match m {
544                0 => return INVTRIG_PI_F / 4.0,           // atan(+INF,+INF)
545                1 => return -INVTRIG_PI_F / 4.0,          // atan(-INF,+INF)
546                2 => return 3.0 * INVTRIG_PI_F / 4.0,     // atan(+INF,-INF)
547                3 => return -3.0 * INVTRIG_PI_F / 4.0,    // atan(-INF,-INF)
548                _ => core::hint::unreachable_unchecked(),
549            }
550        } else {
551            match m {
552                0 => return 0.0f32,             // atan(+...,+INF)
553                1 => return -0.0f32,            // atan(-...,+INF)
554                2 => return INVTRIG_PI_F,        // atan(+...,-INF)
555                3 => return -INVTRIG_PI_F,       // atan(-...,-INF)
556                _ => core::hint::unreachable_unchecked(),
557            }
558        }
559    }
560    // |y/x| > 0x1p26
561    if ix.wrapping_add(26 << 23) < iy || iy == 0x7f800000 {
562        return if (m & 1) != 0 { -INVTRIG_PI_F / 2.0 } else { INVTRIG_PI_F / 2.0 };
563    }
564
565    // z = atan(|y/x|) with correct underflow
566    let z;
567    if (m & 2) != 0 && iy.wrapping_add(26 << 23) < ix {
568        // |y/x| < 0x1p-26, x < 0
569        z = 0.0f32;
570    } else {
571        z = atanf(fabsf(y / x));
572    }
573    match m {
574        0 => z,                                  // atan(+,+)
575        1 => -z,                                 // atan(-,+)
576        2 => INVTRIG_PI_F - (z - INVTRIG_PI_LO_F), // atan(+,-)
577        _ => (z - INVTRIG_PI_LO_F) - INVTRIG_PI_F, // atan(-,-)
578    }
579}