Skip to content

Commit 9b11950

Browse files
ViralBShahclaude
andcommitted
Fix powl() returning NaN on extreme underflow (#334)
In the ld80 (x86 80-bit) powl(), when |y*log2(x)| is enormous the extended-precision reduction reducl(G) computes ldexpl(G, LNXT), which overflows to +-Inf. The following Inf-Inf / Inf+(-Inf) arithmetic (Gb = G - Ga, Ha = reducl(Fb + Gb), Ga + Ha) yields NaN, so the w > MEXP / w < MNEXP over/underflow tests both fail and powl() returns NaN instead of Inf (overflow) or +0 (underflow). Reproducer: powl(0x1.98p-3072l, 0xa.ab43b6dba9a6383p+16364l) returned NaN; glibc correctly returns +0 (underflow). This is a latent bug shared with upstream FreeBSD msun / OpenBSD; there is no upstream fix to import. G already holds y*log2(x) in 1/NXT units, so the final exponent is ldexpl(G, LNXT) to within O(1). Test G for over/underflow before reducl(G) can overflow. A generous 2*MEXP/NXT margin keeps the early guard far from the genuine over/underflow boundary, so borderline values still fall through to the original precise test on w = ldexpl(Ga+Ha, LNXT); only the pathological huge-|G| case is short-circuited. Verified the reproducer now returns +0 and a battery of normal, overflow, underflow, negative-base, non-integer-exponent and boundary cases all match glibc. Full suite (test-double, test-float, regression) passes. Adds test/regression/test-334.c (skips with status 77 when LDBL_MANT_DIG < 64, since the ld80 path is unused there); it fails on the pre-fix code and passes after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent aeef4da commit 9b11950

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

ld80/e_powl.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,27 @@ Fa = reducl(F);
404404
Fb = F - Fa;
405405

406406
G = Fa + w * ya;
407+
408+
/*
409+
* Guard against intermediate overflow in reducl(G). When |y*log2(x)|
410+
* is enormous, reducl(G) computes ldexpl(G, LNXT) which overflows to
411+
* +-Inf; the subsequent Inf-Inf / Inf+(-Inf) arithmetic then yields
412+
* NaN, which defeats the w > MEXP / w < MNEXP tests below and makes
413+
* powl() return NaN instead of Inf (overflow) or +0 (underflow).
414+
* G holds y*log2(x) in 1/NXT units, so the final exponent is
415+
* ldexpl(G, LNXT) to within O(1). Genuine over/underflow occurs at
416+
* |G| ~ MEXP/NXT ~ 16448, vastly below the |G| ~ LDBL_MAX/NXT at
417+
* which reducl(G) overflows. A generous margin (2*MEXP/NXT) keeps
418+
* this guard well clear of the real boundary, so borderline cases
419+
* still fall through to the precise test on w = ldexpl(Ga+Ha, LNXT)
420+
* below, while the pathological huge-|G| case is caught here before
421+
* reducl(G) can overflow.
422+
*/
423+
if( G > (2.0L * MEXP / NXT) )
424+
return (huge * huge); /* overflow */
425+
if( G < (2.0L * MNEXP / NXT) )
426+
return (twom10000 * twom10000); /* underflow */
427+
407428
Ga = reducl(G);
408429
Gb = G - Ga;
409430

test/regression/test-334.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Regression test for issue #334: powl() returns NaN instead of +0 on
3+
* extreme underflow.
4+
* https://github.com/JuliaMath/openlibm/issues/334
5+
*
6+
* For very large |y * log2(x)|, the extended-precision reduction step in
7+
* ld80/e_powl.c (reducl(G), which computes ldexpl(G, LNXT)) overflowed to
8+
* +-Inf. The following Inf-Inf / Inf+(-Inf) arithmetic produced NaN, which
9+
* defeated the w > MEXP / w < MNEXP over/underflow tests and made powl()
10+
* return NaN. The mathematically correct result here is +0 (underflow),
11+
* which is what glibc returns.
12+
*
13+
* This manifests only when long double has more precision than double (the
14+
* x86 80-bit / ld80 path); otherwise powl == pow and the bug cannot occur.
15+
*/
16+
#include <float.h>
17+
#include <math.h>
18+
19+
#include "regress-util.h"
20+
21+
int
22+
main(void)
23+
{
24+
#if LDBL_MANT_DIG < 64
25+
/* long double == double: the ld80 code path is not used. */
26+
return REGRESS_SKIP;
27+
#else
28+
long double x = 0x1.98p-3072l;
29+
long double y = 0xa.ab43b6dba9a6383p+16364l;
30+
long double z = powl(x, y);
31+
32+
/* Must underflow to +0, not return NaN. */
33+
CHECK(!isnan(z));
34+
CHECK(z == 0.0L);
35+
CHECK(!signbit(z)); /* result is +0, not -0 */
36+
return 0;
37+
#endif
38+
}

0 commit comments

Comments
 (0)