Skip to content

Commit 8495e57

Browse files
ViralBShahclaude
andcommitted
ld80: fix spurious tgammal overflow for large negative arguments (#223)
tgammal(-0xd.b6e8f5c28f5c29p+7L) (approx -1755.455) returned +Inf, but the true value is the tiny subnormal 0x0.01dbd551da54538p-16385L. The ld80 implementation made two mistakes near the gamma-overflow threshold MAXGAML: 1. The `q > MAXGAML` early-out was applied to *both* signs of x. MAXGAML is the threshold at which the *positive* gamma overflows; for large negative x, |Gamma(x)| is instead tiny, so this wrongly forced negative arguments down the +Inf overflow path. 2. Just below MAXGAML the reflection code computed `fabsl(z) * stirf(q)` first. For q near 1755, tgamma(q) is enormous and the product overflowed to Inf, after which `PIL/Inf` collapsed to 0 instead of the correct tiny subnormal. Fix: restrict the MAXGAML overflow check to positive x (the only side that overflows), and evaluate the reflection formula as `(PIL / stirf(q)) / z` so the huge tgamma(q) divides pi down to a tiny value before the multiply, avoiding the intermediate overflow. When stirf(q) itself overflows to Inf the quotient correctly underflows toward zero. With this change tgammal(-0xd.b6e8f5c28f5c29p+7L) returns 0x0.01dbd551da54538p-16385L, matching glibc bit-for-bit, and nearby large negative non-integers that previously flushed to 0 now yield the correct subnormals. Positive overflow behavior is unchanged. Adds test/regression/test-223.c (skipped when LDBL_MANT_DIG < 64). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent aeef4da commit 8495e57

2 files changed

Lines changed: 72 additions & 9 deletions

File tree

ld80/e_tgammal.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,6 @@ q = fabsl(x);
233233
if( q > 13.0L )
234234
{
235235
int sign = 1;
236-
if( q > MAXGAML )
237-
goto goverf;
238236
if( x < 0.0L )
239237
{
240238
p = floorl(q);
@@ -250,16 +248,22 @@ if( q > 13.0L )
250248
z = q - p;
251249
}
252250
z = q * sinl( PIL * z );
253-
z = fabsl(z) * stirf(q);
254-
if( z <= PIL/LDBL_MAX )
255-
{
256-
goverf:
257-
return( sign * INFINITY);
258-
}
259-
z = PIL/z;
251+
z = fabsl(z);
252+
/*
253+
* Reflection formula: |tgamma(x)| = pi / (z * tgamma(q)).
254+
* For large negative x, tgamma(q) is enormous, so |tgamma(x)|
255+
* is tiny. Divide pi by tgamma(q) *before* dividing by z to
256+
* avoid an intermediate overflow of z * tgamma(q) that would
257+
* spuriously yield Inf (issue #223). When tgamma(q) is so
258+
* large that stirf(q) overflows to Inf, the quotient correctly
259+
* underflows toward zero.
260+
*/
261+
z = (PIL / stirf(q)) / z;
260262
}
261263
else
262264
{
265+
if( q > MAXGAML )
266+
return( INFINITY );
263267
z = stirf(x);
264268
}
265269
return( sign * z );

test/regression/test-223.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Regression test for issue #223: tgammal spuriously overflows for
3+
* large-ish negative non-integer arguments.
4+
* https://github.com/JuliaMath/openlibm/issues/223
5+
*
6+
* For a large negative non-integer x, |tgamma(x)| is tiny (the reflection
7+
* formula divides pi by the enormous tgamma(1-x)). The ld80 implementation
8+
* applied its positive-overflow threshold (MAXGAML) to negative arguments
9+
* too and, just below that threshold, let z * tgamma(q) overflow to Inf
10+
* before the final division -- both paths wrongly produced +Inf (or a
11+
* flush-to-zero) instead of the correct tiny subnormal.
12+
*
13+
* glibc on x86-64 (the correctness oracle) returns the correctly-rounded
14+
* subnormal 0x0.01dbd551da54538p-16385L for the argument below; pin that.
15+
*/
16+
#include <float.h>
17+
18+
#include "regress-util.h"
19+
20+
/*
21+
* ld80-specific (the fix is in ld80/e_tgammal.c, and the exact hex below holds
22+
* only for the 80-bit format). openlibm provides long double routines only on
23+
* x86 and (linux) aarch64, so every other format must compile to a bare skip
24+
* with no reference to tgammal (it may be absent, which would break the link).
25+
*/
26+
#if LDBL_MANT_DIG != 64
27+
28+
int
29+
main(void)
30+
{
31+
return REGRESS_SKIP;
32+
}
33+
34+
#else
35+
36+
#include <math.h>
37+
38+
int
39+
main(void)
40+
{
41+
long double x = -0xd.b6e8f5c28f5c29p+7L; /* approx -1755.455 */
42+
long double y = tgammal(x);
43+
44+
/* Must be a finite, positive, tiny value -- not Inf and not zero. */
45+
CHECK(isfinite(y));
46+
CHECK(y > 0.0L);
47+
CHECK(y < 1.0L);
48+
49+
/* Correctly-rounded result observed from glibc tgammal on x86-64. */
50+
CHECK(y == 0x0.01dbd551da54538p-16385L);
51+
52+
/* The positive-overflow path (moved out of the shared code) must still
53+
return +Inf for large positive x. */
54+
CHECK(isinf(tgammal(2000.0L)) == 1);
55+
56+
return 0;
57+
}
58+
59+
#endif

0 commit comments

Comments
 (0)