Skip to main content

c/
math_hyperbolic.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// Translated from musl: sinh.c, cosh.c, tanh.c, sinhf.c, coshf.c, tanhf.c,
5// expm1.c, expm1f.c, __expo2.c, __expo2f.c
6//
7// All constants/tables prefixed with HYP_ to avoid collisions with math_exp.rs.
8// All private helpers prefixed with hyper_.
9
10// ============================================================
11// Constants for hyper_expm1 (double precision)
12// ============================================================
13
14const HYP_EPM1_O_THRESHOLD: f64 = asdouble(0x40862E42FEFA39EF);
15const HYP_EPM1_LN2_HI: f64 = asdouble(0x3FE62E42FEE00000);
16const HYP_EPM1_LN2_LO: f64 = asdouble(0x3DEA39EF35793C76);
17const HYP_EPM1_INVLN2: f64 = asdouble(0x3FF71547652B82FE);
18const HYP_EPM1_Q1: f64 = asdouble(0xBFA11111111110F4);
19const HYP_EPM1_Q2: f64 = asdouble(0x3F5A01A019FE5585);
20const HYP_EPM1_Q3: f64 = asdouble(0xBF14CE199EAADBB7);
21const HYP_EPM1_Q4: f64 = asdouble(0x3ED0CFCA86E65239);
22const HYP_EPM1_Q5: f64 = asdouble(0xBE8AFDB76E09C32D);
23
24// ============================================================
25// Constants for hyper_expm1f (float precision)
26// ============================================================
27
28const HYP_EPM1F_LN2_HI: f32 = asfloat(0x3F317180);
29const HYP_EPM1F_LN2_LO: f32 = asfloat(0x3717F7D1);
30const HYP_EPM1F_INVLN2: f32 = asfloat(0x3FB8AA3B);
31const HYP_EPM1F_Q1: f32 = asfloat(0xBD088868);
32const HYP_EPM1F_Q2: f32 = asfloat(0x3ACF3010);
33
34// ============================================================
35// Constants for hyper_expo2 / hyper_expo2f
36// ============================================================
37
38const HYP_EXPO2_KLN2: f64 = asdouble(0x40962066151ADD8B);
39const HYP_EXPO2_SCALE: f64 = asdouble(0x7FC0000000000000);
40
41const HYP_EXPO2F_KLN2: f32 = asfloat(0x4322E3BC);
42const HYP_EXPO2F_SCALE: f32 = asfloat(0x7A000000);
43
44// 2^120 as double (for raising inexact)
45const HYP_TWO120: f64 = asdouble(0x4770000000000000);
46// 2^1023 as double (for overflow scaling in expm1)
47const HYP_TWO1023: f64 = asdouble(0x7FE0000000000000);
48// 2^127 as float (for overflow scaling in expm1f)
49const HYP_TWO127F: f32 = asfloat(0x7F000000);
50
51// ============================================================
52// hyper_expo2: exp(x)/2 for x >= log(DBL_MAX)
53// ============================================================
54
55#[inline]
56fn hyper_expo2(x: f64, sign: f64) -> f64 {
57    let scale = HYP_EXPO2_SCALE;
58    exp(x - HYP_EXPO2_KLN2) * (sign * scale) * scale
59}
60
61// ============================================================
62// hyper_expo2f: expf(x)/2 for x >= log(FLT_MAX)
63// ============================================================
64
65#[inline]
66fn hyper_expo2f(x: f32, sign: f32) -> f32 {
67    let scale = HYP_EXPO2F_SCALE;
68    expf(x - HYP_EXPO2F_KLN2) * (sign * scale) * scale
69}
70
71// ============================================================
72// hyper_expm1: exp(x) - 1 (double precision)
73// ============================================================
74
75#[inline]
76fn hyper_expm1(x: f64) -> f64 {
77    let u = asuint64(x);
78    let hx = ((u >> 32) & 0x7fffffff) as u32;
79    let sign = (u >> 63) as i32;
80
81    // filter out huge and non-finite argument
82    let mut x = x;
83    if hx >= 0x4043687A {
84        if x.is_nan() {
85            return x;
86        }
87        if sign != 0 {
88            return -1.0;
89        }
90        if x > HYP_EPM1_O_THRESHOLD {
91            x *= HYP_TWO1023;
92            return x;
93        }
94    }
95
96    let k: i32;
97    let hi: f64;
98    let lo: f64;
99    let c: f64;
100
101    // argument reduction
102    if hx > 0x3fd62e42 {
103        if hx < 0x3FF0A2B2 {
104            if sign == 0 {
105                hi = x - HYP_EPM1_LN2_HI;
106                lo = HYP_EPM1_LN2_LO;
107                k = 1;
108            } else {
109                hi = x + HYP_EPM1_LN2_HI;
110                lo = -HYP_EPM1_LN2_LO;
111                k = -1;
112            }
113        } else {
114            let kt = if sign == 0 {
115                HYP_EPM1_INVLN2 * x + 0.5
116            } else {
117                HYP_EPM1_INVLN2 * x - 0.5
118            };
119            k = kt as i32;
120            let t = k as f64;
121            hi = x - t * HYP_EPM1_LN2_HI;
122            lo = t * HYP_EPM1_LN2_LO;
123        }
124        x = hi - lo;
125        c = (hi - x) - lo;
126    } else if hx < 0x3c900000 {
127        // |x| < 2^-54, return x
128        if hx < 0x00100000 {
129            force_eval(x as f32);
130        }
131        return x;
132    } else {
133        k = 0;
134        c = 0.0; // unused when k == 0
135    }
136
137    // x is now in primary range
138    let hfx = 0.5 * x;
139    let hxs = x * hfx;
140    let r1 = 1.0 + hxs * (HYP_EPM1_Q1 + hxs * (HYP_EPM1_Q2 + hxs * (HYP_EPM1_Q3 + hxs * (HYP_EPM1_Q4 + hxs * HYP_EPM1_Q5))));
141    let t = 3.0 - r1 * hfx;
142    let e = hxs * ((r1 - t) / (6.0 - x * t));
143    if k == 0 {
144        // c is 0
145        return x - (x * e - hxs);
146    }
147    let e = x * (e - c) - c;
148    let e = e - hxs;
149    // exp(x) ~ 2^k (x_reduced - e + 1)
150    if k == -1 {
151        return 0.5 * (x - e) - 0.5;
152    }
153    if k == 1 {
154        if x < -0.25 {
155            return -2.0 * (e - (x + 0.5));
156        }
157        return 1.0 + 2.0 * (x - e);
158    }
159    let twopk = asdouble(((0x3ff_i64 + k as i64) as u64) << 52);
160    if k < 0 || k > 56 {
161        // suffice to return exp(x)-1
162        let mut y = x - e + 1.0;
163        if k == 1024 {
164            y = y * 2.0 * HYP_TWO1023;
165        } else {
166            y = y * twopk;
167        }
168        return y - 1.0;
169    }
170    let neg_twopk = asdouble(((0x3ff_i64 - k as i64) as u64) << 52);
171    if k < 20 {
172        (x - e + (1.0 - neg_twopk)) * twopk
173    } else {
174        (x - (e + neg_twopk) + 1.0) * twopk
175    }
176}
177
178// ============================================================
179// hyper_expm1f: expf(x) - 1 (float precision)
180// ============================================================
181
182#[inline]
183fn hyper_expm1f(x: f32) -> f32 {
184    let u = asuint(x);
185    let hx = u & 0x7fffffff;
186    let sign = (u >> 31) as i32;
187
188    // filter out huge and non-finite argument
189    let mut x = x;
190    if hx >= 0x4195b844 {
191        if hx > 0x7f800000 {
192            // NaN
193            return x;
194        }
195        if sign != 0 {
196            return -1.0;
197        }
198        if hx > 0x42b17217 {
199            // x > log(FLT_MAX)
200            x *= HYP_TWO127F;
201            return x;
202        }
203    }
204
205    let k: i32;
206    let hi: f32;
207    let lo: f32;
208    let c: f32;
209
210    // argument reduction
211    if hx > 0x3eb17218 {
212        if hx < 0x3F851592 {
213            if sign == 0 {
214                hi = x - HYP_EPM1F_LN2_HI;
215                lo = HYP_EPM1F_LN2_LO;
216                k = 1;
217            } else {
218                hi = x + HYP_EPM1F_LN2_HI;
219                lo = -HYP_EPM1F_LN2_LO;
220                k = -1;
221            }
222        } else {
223            let kt = if sign == 0 {
224                HYP_EPM1F_INVLN2 * x + 0.5
225            } else {
226                HYP_EPM1F_INVLN2 * x - 0.5
227            };
228            k = kt as i32;
229            let t = k as f32;
230            hi = x - t * HYP_EPM1F_LN2_HI;
231            lo = t * HYP_EPM1F_LN2_LO;
232        }
233        x = hi - lo;
234        c = (hi - x) - lo;
235    } else if hx < 0x33000000 {
236        // |x| < 2^-25, return x
237        if hx < 0x00800000 {
238            force_eval(x * x);
239        }
240        return x;
241    } else {
242        k = 0;
243        c = 0.0;
244    }
245
246    // x is now in primary range
247    let hfx = 0.5 * x;
248    let hxs = x * hfx;
249    let r1 = 1.0 + hxs * (HYP_EPM1F_Q1 + hxs * HYP_EPM1F_Q2);
250    let t = 3.0 - r1 * hfx;
251    let e = hxs * ((r1 - t) / (6.0 - x * t));
252    if k == 0 {
253        // c is 0
254        return x - (x * e - hxs);
255    }
256    let e = x * (e - c) - c;
257    let e = e - hxs;
258    // exp(x) ~ 2^k (x_reduced - e + 1)
259    if k == -1 {
260        return 0.5 * (x - e) - 0.5;
261    }
262    if k == 1 {
263        if x < -0.25 {
264            return -2.0 * (e - (x + 0.5));
265        }
266        return 1.0 + 2.0 * (x - e);
267    }
268    let twopk = asfloat(((0x7f + k) as u32) << 23);
269    if k < 0 || k > 56 {
270        // suffice to return exp(x)-1
271        let mut y = x - e + 1.0;
272        if k == 128 {
273            y = y * 2.0 * HYP_TWO127F;
274        } else {
275            y = y * twopk;
276        }
277        return y - 1.0;
278    }
279    let neg_twopk = asfloat(((0x7f - k) as u32) << 23);
280    if k < 23 {
281        (x - e + (1.0 - neg_twopk)) * twopk
282    } else {
283        (x - (e + neg_twopk) + 1.0) * twopk
284    }
285}
286
287// ============================================================
288// Public API: sinh, cosh, tanh (double precision)
289// ============================================================
290
291#[no_mangle]
292pub extern "C" fn sinh(x: f64) -> f64 {
293    let u = asuint64(x);
294    let mut h = 0.5f64;
295    if u >> 63 != 0 {
296        h = -h;
297    }
298    // |x|
299    let u = u & 0x7FFFFFFFFFFFFFFF;
300    let absx = asdouble(u);
301    let w = (u >> 32) as u32;
302
303    // |x| < log(DBL_MAX)
304    if w < 0x40862e42 {
305        let t = hyper_expm1(absx);
306        if w < 0x3ff00000 {
307            if w < 0x3ff00000 - (26 << 20) {
308                // note: inexact and underflow are raised by expm1
309                // note: this branch avoids spurious underflow
310                return x;
311            }
312            return h * (2.0 * t - t * t / (t + 1.0));
313        }
314        // note: |x|>log(0x1p26)+eps could be just h*exp(x)
315        return h * (t + t / (t + 1.0));
316    }
317
318    // |x| > log(DBL_MAX) or nan
319    // note: the result is stored to handle overflow
320    hyper_expo2(absx, 2.0 * h)
321}
322
323#[no_mangle]
324pub extern "C" fn cosh(x: f64) -> f64 {
325    let u = asuint64(x);
326    // |x|
327    let u = u & 0x7FFFFFFFFFFFFFFF;
328    let x = asdouble(u);
329    let w = (u >> 32) as u32;
330
331    // |x| < log(2)
332    if w < 0x3fe62e42 {
333        if w < 0x3ff00000 - (26 << 20) {
334            // raise inexact if x!=0
335            force_eval(x + HYP_TWO120);
336            return 1.0;
337        }
338        let t = hyper_expm1(x);
339        return 1.0 + t * t / (2.0 * (1.0 + t));
340    }
341
342    // |x| < log(DBL_MAX)
343    if w < 0x40862e42 {
344        let t = exp(x);
345        // note: if x>log(0x1p26) then the 1/t is not needed
346        return 0.5 * (t + 1.0 / t);
347    }
348
349    // |x| > log(DBL_MAX) or nan
350    // note: the result is stored to handle overflow
351    hyper_expo2(x, 1.0)
352}
353
354#[no_mangle]
355pub extern "C" fn tanh(x: f64) -> f64 {
356    let u = asuint64(x);
357    let sign = (u >> 63) as i32;
358    // x = |x|
359    let u = u & 0x7FFFFFFFFFFFFFFF;
360    let x = asdouble(u);
361    let w = (u >> 32) as u32;
362
363    let t: f64;
364    if w > 0x3fe193ea {
365        // |x| > log(3)/2 ~= 0.5493 or nan
366        if w > 0x40340000 {
367            // |x| > 20 or nan
368            // note: this branch avoids raising overflow
369            t = 1.0 - 0.0 / x;
370        } else {
371            let tmp = hyper_expm1(2.0 * x);
372            t = 1.0 - 2.0 / (tmp + 2.0);
373        }
374    } else if w > 0x3fd058ae {
375        // |x| > log(5/3)/2 ~= 0.2554
376        let tmp = hyper_expm1(2.0 * x);
377        t = tmp / (tmp + 2.0);
378    } else if w >= 0x00100000 {
379        // |x| >= 0x1p-1022, up to 2ulp error in [0.1,0.2554]
380        let tmp = hyper_expm1(-2.0 * x);
381        t = -tmp / (tmp + 2.0);
382    } else {
383        // |x| is subnormal
384        // note: the branch above would not raise underflow in [0x1p-1023,0x1p-1022)
385        force_eval(x as f32);
386        t = x;
387    }
388    if sign != 0 { -t } else { t }
389}
390
391// ============================================================
392// Public API: sinhf, coshf, tanhf (float precision)
393// ============================================================
394
395#[no_mangle]
396pub extern "C" fn sinhf(x: f32) -> f32 {
397    let u = asuint(x);
398    let mut h = 0.5f32;
399    if u >> 31 != 0 {
400        h = -h;
401    }
402    // |x|
403    let u = u & 0x7fffffff;
404    let absx = asfloat(u);
405    let w = u;
406
407    // |x| < log(FLT_MAX)
408    if w < 0x42b17217 {
409        let t = hyper_expm1f(absx);
410        if w < 0x3f800000 {
411            if w < 0x3f800000 - (12 << 23) {
412                return x;
413            }
414            return h * (2.0 * t - t * t / (t + 1.0));
415        }
416        return h * (t + t / (t + 1.0));
417    }
418
419    // |x| > logf(FLT_MAX) or nan
420    hyper_expo2f(absx, 2.0 * h)
421}
422
423#[no_mangle]
424pub extern "C" fn coshf(x: f32) -> f32 {
425    let u = asuint(x);
426    // |x|
427    let u = u & 0x7fffffff;
428    let x = asfloat(u);
429    let w = u;
430
431    // |x| < log(2)
432    if w < 0x3f317217 {
433        if w < 0x3f800000 - (12 << 23) {
434            force_eval(x + asfloat(0x7B800000)); // x + 2^120f
435            return 1.0;
436        }
437        let t = hyper_expm1f(x);
438        return 1.0 + t * t / (2.0 * (1.0 + t));
439    }
440
441    // |x| < log(FLT_MAX)
442    if w < 0x42b17217 {
443        let t = expf(x);
444        return 0.5 * (t + 1.0 / t);
445    }
446
447    // |x| > log(FLT_MAX) or nan
448    hyper_expo2f(x, 1.0)
449}
450
451#[no_mangle]
452pub extern "C" fn tanhf(x: f32) -> f32 {
453    let u = asuint(x);
454    let sign = (u >> 31) as i32;
455    // x = |x|
456    let u = u & 0x7fffffff;
457    let x = asfloat(u);
458    let w = u;
459
460    let t: f32;
461    if w > 0x3f0c9f54 {
462        // |x| > log(3)/2 ~= 0.5493 or nan
463        if w > 0x41200000 {
464            // |x| > 10
465            t = 1.0 + 0.0 / x;
466        } else {
467            let tmp = hyper_expm1f(2.0 * x);
468            t = 1.0 - 2.0 / (tmp + 2.0);
469        }
470    } else if w > 0x3e82c578 {
471        // |x| > log(5/3)/2 ~= 0.2554
472        let tmp = hyper_expm1f(2.0 * x);
473        t = tmp / (tmp + 2.0);
474    } else if w >= 0x00800000 {
475        // |x| >= 0x1p-126
476        let tmp = hyper_expm1f(-2.0 * x);
477        t = -tmp / (tmp + 2.0);
478    } else {
479        // |x| is subnormal
480        force_eval(x * x);
481        t = x;
482    }
483    if sign != 0 { -t } else { t }
484}