Skip to content

Fix incomplete gamma convergence for GAMMA.DIST / CHISQ.DIST - #4945

Merged
oleibman merged 4 commits into
PHPOffice:masterfrom
binggao1230:fix-incomplete-gamma-convergence
Aug 5, 2026
Merged

oleibman merged 4 commits into
PHPOffice:masterfrom
binggao1230:fix-incomplete-gamma-convergence

Conversation

@binggao1230

Copy link
Copy Markdown
Contributor

GammaBase::incompleteGamma summed a fixed 32-term power series with no convergence test, so every function built on it went badly wrong once the series argument reached ~32:

CHISQ.DIST.RT(80, 4)       -> 0.806    (Excel/mpmath: 1.742e-16)
CHISQ.DIST.RT(200, 20)     -> ~1.0     (should be 1.125e-31)
GAMMA.DIST(80, 2, 1, TRUE) -> 5.3e-9   (should be 1.0)

That primitive backs GAMMA.DIST/GAMMADIST, CHISQ.DIST(.RT), GAMMAINV and CHISQ.INV, so all of them were wrong on realistic inputs (any chi-square with x/2 ≳ 32).

Replaced it with the convergence-tested regularized incomplete gamma: series P(a,x) for x < a+1, continued fraction Q(a,x) for x ≥ a+1. CHISQ.DIST.RT now uses Q directly, so the right tail avoids 1 - P cancellation. A correct copy of this algorithm already existed privately in ChiSquared (used by CHISQ.INV left tail) and is now folded onto the shared method.

Expected values come from mpmath (gammainc regularized, dps 30 ≡ 35); small-x results are unchanged. Three fixtures had encoded the old wrong output (including an impossible CHISQ.INV.RT of 100.0) and were corrected. Full Statistical test suite is green.

binggao1230 added a commit to binggao1230/PhpSpreadsheet that referenced this pull request Jul 28, 2026
@oleibman

oleibman commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Please add some tests for incompleteGamma. You replaced the code in it, but you also eliminated all calls to it, and it is a public function, so we just want to make sure there are no BC breaks (for when the old code was adequate).

@binggao1230
binggao1230 force-pushed the fix-incomplete-gamma-convergence branch from ce7decd to 8441e89 Compare August 3, 2026 11:47
binggao1230 added a commit to binggao1230/PhpSpreadsheet that referenced this pull request Aug 3, 2026
@binggao1230

Copy link
Copy Markdown
Contributor Author

Added tests/PhpSpreadsheetTests/Calculation/Statistical/Distributions/GammaBaseTest.php, testing incompleteGamma() directly against mpmath's gammainc(a, 0, x) reference values (it genuinely has no callers left, so nothing else exercises it). Included the deep-tail case (a=4, x=80) from the original bug report — verified it fails on the pre-fix code and passes on the current one. Rebased onto current master.

@oleibman
oleibman added this pull request to the merge queue Aug 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 4, 2026
@oleibman

oleibman commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Well, this is a fine kettle of fish. I merged your GAMMAINV PR, and then I tried to merge this one. Which was almost successful, but ... it broke one of your new tests from the other PR, testGammaInvUnreachableTailStaysBounded. I took a look, but the change to GammaBase is just too extensive for me to pin down the problem. I hope it will be easier for you to figure out.

The incomplete gamma primitive used a fixed 32-term power series with no
convergence test, so GAMMA.DIST, GAMMADIST, CHISQ.DIST(.RT), GAMMAINV and
CHISQ.INV were grossly wrong once the series argument reached ~32
(e.g. CHISQ.DIST.RT(80, 4) returned 0.806 instead of 1.74e-16).

Replace it with the standard convergence-tested regularized incomplete
gamma: series P(a,x) for x < a+1, continued fraction Q(a,x) for x >= a+1.
CHISQ.DIST.RT now uses Q directly so the right tail stays free of
1 - P cancellation. Consolidates the duplicate copy that already existed
privately in ChiSquared onto the shared primitive.
It has no remaining callers (kept only for BC) so nothing else exercises
it. Values checked against mpmath's gammainc(a, 0, x); the deep-tail case
(a=4, x=80) reproduces the convergence bug this PR fixes.
Merging PHPOffice#4946 (bracket expansion) with PHPOffice#4945 (accurate incomplete
gamma) means p=0.9999999 no longer hits the alpha*beta*5 safety
ceiling -- the corrected regularizedGammaP/Q resolve the true root.
Fold it into the extreme-tail data provider against the closed-form
-ln(1-p) check instead of asserting the stale clamped bound.
@binggao1230
binggao1230 force-pushed the fix-incomplete-gamma-convergence branch from 7e798f5 to a0e67af Compare August 4, 2026 06:28
@binggao1230

Copy link
Copy Markdown
Contributor Author

Found it — the two PRs interact. testGammaInvUnreachableTailStaysBounded asserted p=0.9999999 stays clamped at alphabeta5, but that was only true because the old incompleteGamma couldn't resolve the tail. With #4945's corrected series/continued-fraction, GAMMA.INV now correctly reaches 16.118... (=-ln(1e-7)) instead of clamping. Rebased onto master and folded that case into providerGammaInvExtremeTail against the closed-form check; rest of the statistical suite (931 tests) still green.

@oleibman

oleibman commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

I am glad you found the problem. You now no longer have an "unreachable tail" test (existing test there is moved to a reachable one). I am willing to proceed without one, but not before asking if you might be able to think up a new test for that case. Let me know either way and I will proceed accordingly.

@binggao1230

Copy link
Copy Markdown
Contributor Author

Went hunting for a genuinely-unreachable case, and it's not probability depth — checked down to p=0.999999999999999 (smallest representable gap below 1), all resolve to the closed-form value now. It's shape magnitude instead: calculateInverse's Newton step calls gammaValue($a) directly for the PDF, which overflows past alpha≈172 (Gamma(172) > DBL_MAX), so the PDF comes out NAN and GAMMA.INV(0.5, 200, 1) returns #NUM! right now on this branch. CHISQ.INV doesn't have this problem — inverseLeftTailCalculation bisects purely off regularizedGammaP, never touching gammaValue.

That's pre-existing and orthogonal to this PR's series/CF fix, so I'd rather open it as its own follow-up than scope-creep this one. Let me know if you'd want a bounded-behavior test added here instead in the meantime.

@oleibman

oleibman commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Thank you for doing the research. There is no need to add a new test to this PR.

@oleibman
oleibman added this pull request to the merge queue Aug 5, 2026
Merged via the queue into PHPOffice:master with commit 47aca38 Aug 5, 2026
13 of 14 checks passed
@oleibman

oleibman commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Thank you for your contribution.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants