Skip to content

Commit 60cfcf3

Browse files
ViralBShahclaude
andcommitted
Fix expm1l returning NaN for large finite arguments (#306)
For arguments just below the overflow threshold, expm1l reconstructed the result as 2^k * qx + (2^k - 1) with k == LDBL_MAX_EXP (16384). ldexpl(1, k) then overflowed to +Inf, and the subsequent (+Inf) + (-Inf) (qx is negative when the reduced remainder is negative) produced NaN, even though the true result is finite and near LDBL_MAX. When k is large enough that 2^k overflows, instead form exp(remainder ln 2) = qx + 1 (which lies near 1) and scale its exponent directly via scalbnl, so no intermediate value overflows. This is algebraically identical to the existing formula; the normal path is unchanged. Result now matches glibc bit-for-bit on x86-64 (80-bit long double), and slightly larger arguments still overflow to +Inf as before. Adds regression test test-306. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d1957d2 commit 60cfcf3

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

ld80/s_expm1l.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
*
5858
*/
5959

60+
#include <float.h>
61+
6062
#include <openlibm_math.h>
6163

6264
static const long double MAXLOGL = 1.1356523406294143949492E4L;
@@ -131,8 +133,17 @@ qx = x + (0.5 * xx + xx * px / qx);
131133

132134
/* exp(x) = exp(k ln 2) exp(remainder ln 2) = 2^k exp(remainder ln 2).
133135
We have qx = exp(remainder ln 2) - 1, so
134-
exp(x) - 1 = 2^k (qx + 1) - 1 = 2^k qx + 2^k - 1. */
135-
px = ldexpl(1.0L, k);
136-
x = px * qx + (px - 1.0);
136+
exp(x) - 1 = 2^k (qx + 1) - 1 = 2^k qx + 2^k - 1.
137+
138+
For large k, 2^k overflows even though the final result is finite
139+
(the argument is just below the overflow threshold). In that regime
140+
form exp(remainder ln 2) = qx + 1 first (which lies near 1) and scale
141+
the exponent directly, so no intermediate value overflows. */
142+
if (k >= LDBL_MAX_EXP) {
143+
x = scalbnl(qx + 1.0L, k) - 1.0L;
144+
} else {
145+
px = ldexpl(1.0L, k);
146+
x = px * qx + (px - 1.0);
147+
}
137148
return x;
138149
}

test/regression/test-306.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Regression test for issue #306: expm1l overflows internally and returns NaN
3+
* for large arguments that are still below the overflow threshold.
4+
* https://github.com/JuliaMath/openlibm/issues/306
5+
*
6+
* For x just below MAXLOG the true result is finite (near LDBL_MAX), but the
7+
* old reconstruction formed 2^k (with k == LDBL_MAX_EXP) which overflowed to
8+
* +Inf; the subsequent (+Inf) + (-Inf) yielded NaN. The fix scales the
9+
* exponent of exp(remainder) directly in that regime.
10+
*
11+
* The expected hex values below are bit-for-bit what the system glibc expm1l
12+
* returns on x86-64 (LDBL_MANT_DIG == 64). Only meaningful for the 80-bit
13+
* long double format, so skip elsewhere.
14+
*/
15+
#include <float.h>
16+
#include <math.h>
17+
18+
#include "regress-util.h"
19+
20+
int
21+
main(void)
22+
{
23+
#if LDBL_MANT_DIG < 64
24+
return REGRESS_SKIP;
25+
#else
26+
long double x = 0x2.c5c85fdf170c604cp+12l; /* ~11346.6, just below threshold */
27+
long double y = expm1l(x);
28+
29+
/* Must be a large finite value, not NaN/Inf. */
30+
CHECK(!isnanl(y));
31+
CHECK(isfinite(y));
32+
CHECK(y > 0.0L);
33+
34+
/* Exact value matching glibc. */
35+
CHECK(y == 0xf.ffffcfce79e56d5p+16380l);
36+
37+
/* A slightly larger argument must still overflow to +Inf. */
38+
CHECK(isinf(expm1l(11357.0L)) == 1);
39+
40+
return 0;
41+
#endif
42+
}

0 commit comments

Comments
 (0)