Skip to content

Commit a279390

Browse files
authored
Preserve logGamma accuracy near one (#11130)
Avoid recurrence rounding that loses information when logGamma's result is close to zero. At representable neighbors of one, this can produce roughly 73% relative error above one and 246% below one with both 53-bit and 64-bit real significands. Evaluate a degree-ten Taylor expansion for |x - 1| <= 2^-13, using the exactly representable displacement from one. Preserve positive zero at one and bound the truncation error sufficiently for binary128 precision. Add regression coverage for adjacent values, both approximation endpoints, and intermediate displacements using independent high-precision references. Local benchmarks show substantially faster evaluation near one. AI-Assisted-By: Codex (GPT-6 Astra Medium)
1 parent 918c061 commit a279390

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

‎std/internal/math/gammafunction.d‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,30 @@ real logGamma(real x)
641641

642642
if ( x < LN_GAMMA_STIRLING_LB )
643643
{
644+
// Near one, recurrence rounds away information needed for the small
645+
// result. Evaluate logGamma(1+h) directly, where h is exact.
646+
const h = x - 1;
647+
if (fabs(h) <= 0x1p-13L)
648+
{
649+
if (h == 0)
650+
return 0.0L;
651+
// logGamma(1+h) = -EulerGamma*h + sum((-1)^n*zeta(n)*h^n/n).
652+
// At |h| <= 2^-13, the degree-ten remainder is below 2^-146,
653+
// also sufficient relative to the result for binary128.
654+
static immutable real[9] coefficients = [
655+
0.8224670334241132182362075833230125946L,
656+
-0.4006856343865314284665793871704833303L,
657+
0.2705808084277845478790009241352919757L,
658+
-0.2073855510286739852662730972914068336L,
659+
0.1695571769974081899524196549651534213L,
660+
-0.144049896768846118119971078549970965657L,
661+
0.1255096695247430424223356548135815582L,
662+
-0.111334265869564690490872529914712451165L,
663+
0.1000994575127818085337145958900319017L,
664+
];
665+
return h * (-EULERGAMMA + h * poly(h, coefficients));
666+
}
667+
644668
z = 1.0L;
645669
nx = floor( x + 0.5L );
646670
f = x - nx;
@@ -693,6 +717,30 @@ real logGamma(real x)
693717
return q ;
694718
}
695719

720+
@safe pure nothrow @nogc unittest
721+
{
722+
// Regression for https://github.com/dlang/phobos/issues/11128.
723+
foreach (x; [nextDown(1.0L), nextUp(1.0L)])
724+
{
725+
const h = x - 1;
726+
assert(isClose(logGamma(x), -EULERGAMMA * h, 8 * real.epsilon));
727+
}
728+
729+
// Independent high-precision references from recurrence and Stirling's
730+
// expansion. Include both endpoints of the near-one approximation.
731+
static immutable real[2][] cases = [
732+
[1 - 0x1p-13L, 0.00007047315303717008615499045661742960649129L],
733+
[1 + 0x1p-13L, -0.00007044864160936656733821449407577769386531L],
734+
[1 - 0x1p-20L, 5.504765026737052437975868352636020011575e-7L],
735+
[1 + 0x1p-20L, -5.504750066148866790922434423491111098145e-7L],
736+
[1 - 0x1p-40L, 5.249745890089624390528065763412892202040e-13L],
737+
[1 + 0x1p-40L, -5.249745890076017814837817174241475433141e-13L],
738+
];
739+
foreach (test; cases)
740+
assert(isClose(logGamma(test[0]), test[1], 8 * real.epsilon));
741+
assert(isIdentical(logGamma(1.0L), 0.0L));
742+
}
743+
696744
@safe unittest
697745
{
698746
assert(isIdentical(logGamma(NaN(0xDEF)), NaN(0xDEF)));

0 commit comments

Comments
 (0)