Skip to content

Fix powl() returning NaN instead of +0 on extreme underflow (#334)#351

Merged
ViralBShah merged 1 commit into
masterfrom
fix-334-powl-overflow
Jun 23, 2026
Merged

Fix powl() returning NaN instead of +0 on extreme underflow (#334)#351
ViralBShah merged 1 commit into
masterfrom
fix-334-powl-overflow

Conversation

@ViralBShah

Copy link
Copy Markdown
Member

Fixes #334.

powl(0x1.98p-3072L, 0xa.ab43b6dba9a6383p+16364L) returned NaN; glibc returns +0 (underflow).

Root cause

In ld80/e_powl.c, powl forms G = y*log2(x) in 1/NXT units (here G ≈ -2^16379). The reduction helper reducl(G) computes ldexpl(G, LNXT) = G*32 ≈ -2^16384, which overflows to -Inf. That cascades (Gb = G - (-Inf) = +Inf; w = ldexpl(-Inf + +Inf, …) = NaN), so the over/underflow tests w > MEXP / w < MNEXP are both false and the routine falls through returning NaN. The true result underflows to +0. The bug is shared verbatim with current FreeBSD/OpenBSD msun, so there is no upstream patch to import.

Fix

Short-circuit over/underflow on G (which already holds the final exponent to within O(1)) before reducl(G) can overflow, with a generous 2*MEXP/NXT margin so genuine borderline values still reach the original precise test:

G = Fa + w * ya;
if (G >  2.0L*MEXP/NXT)  return huge*huge;          /* overflow  */
if (G <  2.0L*MNEXP/NXT) return twom10000*twom10000;/* underflow */
Ga = reducl(G);

Verification

  • Reproducer now returns 0x0p+0, matching glibc.
  • 19-case battery (normal, overflow→Inf, underflow→+0, negative bases, non-integer exponents, 1^y, x^0, and four over/underflow boundary cases at y=16383.5/16384.5/−16445/−16500) all match glibc, including exact boundary classification.
  • Full make test passes (double + float + exception-flag suites).
  • New test/regression/test-334.c asserts the result is +0 and not NaN; verified it fails before this fix and passes after (skips where long double == double).

Stacked on the regression-harness PR #350 (provides test/regression/).

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.11%. Comparing base (9dc4c05) to head (0654492).

Files with missing lines Patch % Lines
ld80/e_powl.c 50.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #351      +/-   ##
==========================================
- Coverage   72.12%   72.11%   -0.02%     
==========================================
  Files         233      233              
  Lines        6135     6139       +4     
  Branches     1607     1609       +2     
==========================================
+ Hits         4425     4427       +2     
- Misses       1417     1419       +2     
  Partials      293      293              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ViralBShah ViralBShah force-pushed the fix-334-powl-overflow branch 2 times, most recently from 9b11950 to d437edc Compare June 22, 2026 23:50
@ViralBShah ViralBShah force-pushed the fix-334-powl-overflow branch from d437edc to 62d3ef1 Compare June 23, 2026 00:08
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>
@ViralBShah ViralBShah force-pushed the fix-334-powl-overflow branch from 62d3ef1 to 0654492 Compare June 23, 2026 01:10
@ViralBShah ViralBShah merged commit 2afd671 into master Jun 23, 2026
24 checks passed
@ViralBShah ViralBShah deleted the fix-334-powl-overflow branch June 23, 2026 01:23
@zimmermann6

Copy link
Copy Markdown

good work! I'll check that with the next release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

powl yields NaN instead of +0

2 participants