540 Commits

Author SHA1 Message Date
oleibman 918f32d857 Google Is Messing Up a Test with Redirects
Try github instead.
2026-09-04 10:27:18 -07:00
oleibman 329a1b4730 Merge branch 'master' into stan82 2026-09-01 22:39:41 -07:00
oleibman 9e11effcad Update Phpstan
Also address problems reported in PR #4974 when Phpstan was run with an earlier Php version than our CI uses.
2026-09-01 22:24:34 -07:00
DIReports 24fe547e0b Remove trailing empty line in ParseFormulaTest 2026-08-20 09:28:59 +02:00
DIReports 2c83284f42 Move whole-column VLOOKUP tests to VLookupTest
Move testVlookupWholeColumnRange and providerVlookupWholeColumnRange
from ParseFormulaTest to the correct location VLookupTest, which
extends AllSetupTeardown. Adapted to use $this->getSpreadsheet()
instead of managing the Spreadsheet lifecycle manually.
2026-08-20 09:27:25 +02:00
Dick Ittmann 67c0c51810 Apply PHP-CS-Fixer formatting 2026-08-19 18:08:34 +02:00
Dick Ittmann 89c1b9f2fa Move tests to ParseFormulaTest 2026-08-19 18:01:05 +02:00
DIReports 5e91ce2ba2 Fix VLOOKUP #N/A with whole-column ranges when end column has no data
When resolving the end reference of a whole-column range (e.g. $A:$F),
getHighestDataRow($col) was called for the specific end column. If that
column contains no data it returns 1, producing an inverted range such as
A4:F1. This caused VLOOKUP and similar functions to return #N/A when the
formula used a whole-column reference across sheets where data only exists
in the left-hand columns.

Fix: call getHighestDataRow() without a column argument so the overall
highest data row across all columns is used for the end reference.

Reproducer:
  Sheet2!A:D has data in cols A–C only; $A:$F produced A4:F1 → #N/A.
  After fix: A1:F4 → VLOOKUP finds the value correctly.
2026-08-19 16:25:33 +02:00
oleibman 96d442ab66 Php8.6 Deprecation in Reflection::invokeArgs
Affects one test member, no source code.
2026-08-16 20:55:28 -07:00
oleibman 585d3c0ce4 Merge branch 'master' into stancomments 2026-08-09 02:42:11 -07:00
oleibman 3518d40436 Merge branch 'master' into fix-betainv-underflow-false-convergence 2026-08-08 23:59:16 -07:00
Vincent Gao 52da37f4a7 Fix gamma-family overflow for large shape parameters
GAMMA.INV silently returned the first bisection midpoint for alpha in
~[143, 171.62] (e.g. GAMMA.INV(0.5, 143, 1) gave 358.0 instead of
142.667) and #NUM! above that, because the Newton-step pdf evaluates
Gamma(a), b**a and value**(a-1) in linear domain, all of which overflow
even though the density itself is a small representable number. The same
pattern breaks the GAMMA.DIST, CHISQ.DIST and F.DIST densities and
GAMMALN, which computed log(Gamma(x)) through Gamma(x).

Evaluate these in log domain via the existing logGamma, and scale the
incomplete-gamma series/continued-fraction iteration cap as O(sqrt(a)),
which both expansions need to converge near x ~ a once the shape is
large (GAMMA.INV drifted from the true quantile above alpha ~5000 and
returned alpha+1 by alpha=10000; same for CHISQ.INV at high df).
2026-08-07 01:14:45 +02:00
Vincent Gao f687d261e8 Fix BETAINV false convergence when the Beta CDF underflows
calculateInverse() treated a CDF value of exactly 0.0 as "the guess is the
root" and collapsed the bracket with $b = $a, ending the search. inverse()
already rejects probability <= 0, so a CDF of 0.0 inside the bisection can
only be a float64 underflow at a guess far below the root - never an exact
hit. It is just an ordinary "guess too low" and belongs in the existing
else branch.

The underflow is reached whenever a probe lands many standard deviations
from the mean, so the search collapses after two or three iterations and
returns whichever midpoint it was holding. BETAINV(0.5, 5000, 5000) gave
0.25, though Beta(a, a) is symmetric and its median is exactly 0.5;
BETAINV(0.5, 20000, 3) gave 0.5 against a true 0.99986. Onset is around
alpha = 1080 for beta = 1, where the closed form 0.5 ** (1 / alpha) is
available to check against.

That test also happened to stop the search for shapes where incompleteBeta
declines to evaluate at all and returns 0 for every x, so inverse() now
rejects alpha + beta above that documented limit up front: the CDF is
identically zero there, so no quantile exists to search for.

Tests cover a shape x probability grid against scipy reference values plus
three checks that need no external oracle: the symmetric median, the
Beta(alpha, 1) closed form, and the BETADIST round trip / mirror identity.
2026-08-05 12:28:05 +02:00
oleibman 608da6024e Merge branch 'master' into stancomments 2026-08-04 20:56:58 -07:00
Vincent Gao a0e67af496 Update the now-reachable extreme-tail case for GAMMA.INV
Merging #4946 (bracket expansion) with #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.
2026-08-04 08:28:27 +02:00
Vincent Gao 270456a20e test: cover GammaBase::incompleteGamma() directly
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.
2026-08-04 08:25:52 +02:00
gaoflow e88cd615cc Fix incomplete gamma convergence for GAMMA.DIST / CHISQ.DIST
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.
2026-08-04 08:25:36 +02:00
oleibman a32a22a481 Merge branch 'master' into stancomments 2026-08-03 22:42:32 -07:00
oleibman 40911aa104 Update Phpstan and Tcpdf
Many problems with Dependabot this month. Phpstan introduced a lot of new "errors". These are now fixed or annotated. I combined this with a change to require comments for `phpstan-ignore`. These won't always be useful, but I think requiring them makes sense.

Tcpdf is more of a non-update. Our composer.json specified `^6.5`. For some reason, Dependabot decided it was okay to change that to `^6.5||^7.0`, which seems presumptuous. (One of the triggers was probably the elimination of Php8.1, since the new product requires 8.2+.) Tcpdf is nominally deprecated, replaced by tc-lib-pdf. Tcpdf 7 passes control to the new product. However, the upgrade is not straightforward. The user needs to supply font files which were formerly distributed with the product, and a code change to define a (shudder) global constant is required. Consequently, Dependabot's upgrade failed its unit tests. While I may evaluate what might be needed at some point in the future, for now I am just updating composer.json to reject Tcpdf 7+.
2026-08-01 22:06:38 -07:00
Vincent Gao c497c12eb3 Evaluate GAMMA.INV via spreadsheet formula in tests; document bounded tail fallback 2026-07-29 04:59:19 +02:00
gaoflow 2a82540c1f Expand GAMMAINV bracket so tail quantiles are not clamped
GammaBase::calculateInverse fixed its upper bound at alpha*beta*5, so any
GAMMA.INV/GAMMAINV quantile larger than that was clamped to it: e.g.
GAMMAINV(0.9999, 1, 1) returned 5 rather than ~9.2103, breaking the
round-trip GAMMADIST(GAMMAINV(p)) == p.

Grow the upper bound geometrically until it brackets the root. If the CDF
stops increasing first (the series approximation is past its usable range)
keep the original bound instead of expanding into it, which also stops a
probability the series cannot reach from running the bound away.
2026-07-28 12:17:22 +02:00
oleibman 2601644333 Merge pull request #4907 from oleibman/nophp81
Drop Support for Php 8.1
2026-07-12 14:44:00 +00:00
oleibman dc455c45f9 Possibly Acceptable 2026-07-12 03:20:25 -07:00
oleibman 40bdbb3c90 WIP Testing 2026-07-12 01:53:11 -07:00
oleibman 0b136d1e6e Blip 2026-07-12 00:07:04 -07:00
oleibman 5d9c5a667f Not Sure Why Test Failed
Works locally, not on Github. Disabling for now, will continue to research.
2026-07-12 00:00:49 -07:00
oleibman 7ef7b25e85 Merge commit from fork 2026-07-11 23:13:06 -07:00
oleibman 1388efb080 Drop Support for Php 8.1
This PR will be merged at the end of this month. As the README says, we maintain support for Php Versions for six months beyond their end of life. That time has now arrived for 8.1.
2026-06-08 09:15:57 -07:00
oleibman 3311a223fe Merge branch 'master' into perf/formula-ast-parsing 2026-04-14 13:09:13 -07:00
oleibman 4a476071b3 Merge pull request #4850 from oleibman/updatestan
Update PhpStan
2026-04-05 07:15:00 +00:00
oleibman 78a151d340 Merge branch 'master' into xlookup 2026-04-04 23:39:55 -07:00
oleibman 7e9c3814c4 Update PhpStan
Too many errors when Dependabot tried. These seem mostly to involve `implode`, whose use is often embedded in other function calls. For that reason, these changes include a higher proportion than usual of `// @phpstan-ignore-line`.
2026-04-03 13:00:27 -07:00
kemo c319f893d5 Refactor formula token cache to instance properties, opt-in by default 2026-03-26 09:23:26 +01:00
oleibman 616e13c1e6 Coverage
I think there may still be a couple of uncovered statements after this.
2026-03-25 23:56:55 -07:00
oleibman 93c5c51dd5 XLOOKUP
Fix #1453, which went stale in 2000, which I unstaled in 2024, and which I finally got to. The code, and several unit tests, was substantially generated by AI, my first foray into that frontier.

Trait `ArrayEnabled` is not able to handle this function because it has too many array parameters. I would have liked to update it to handle this, but that seemed too difficult. I have let the code do its own array handling; it looks a little kludgey but seems to do the job. I may return to this at some point.
2026-03-25 21:44:54 -07:00
oleibman 70e94a647d Move Test to More Sensible Location 2026-03-12 10:41:43 -07:00
oleibman 5aeca473e6 Confusion Checking for Union Arguments
Fix #4832. PR #4657 added support for passing union arguments to functions. User reports a problem with a peculiar formula afterwards. See issue for details - the very loose restrictions placed on worksheet names can lead to an ambiguous situation for the parser.

This problem arose with a function whose first argument was a cell address *including sheet name*. Parser is changed to try to avoid this situation. If the regular expression which tells us we have a potential need for union has a left parenthesis in the "sheet name" without a right parenthesis, then it will no longer try to treat the formula as containing union arguments except in the unlikely event that "sheet name" truly is a worksheet title in the spreadsheet. This feels pretty kludgey, but it solves the problem at hand, and seems unlikely to cause problems. There may still be edge cases more subtle than the one in the issue; I am satisfied to wait for reports of such.
2026-03-11 20:14:12 -07:00
kemo d9358c6714 Add formula token cache to reduce repeated parsing of identical formulas 2026-03-10 15:06:30 +01:00
oleibman 40f7cd00db Consistent Usage of Column and Row Limits
We have identical constants defined in several places, and use literals in others. We aren't consistent in checking limits. This PR makes the use of the constants in Cell/AddressRange the "official" source, deprecates all other constants, and substitutes the constants wherever literals are used. A number of different edge case tests are added.

During testing, I discovered that `columnIndexFromString` correctly throws an exception for 4-character string, but allows `XFE` through `ZZZ`, all of which are also invalid. There are similar inconsistencies with related routines, and this PR attempts to make them operate consistently. One suprise is that throwing for `row=0` causes serious regression problems, so it continues to be permitted (but the high row limit is enforced).

Further, Reference Helper sometimes dips into negative numbers, resulting in totally unexpected results (-1 affects column Z, -2 column Y, etc.). It is changed to ignore rows and columns outside the limits.
2026-03-03 19:04:11 -08:00
oleibman 14513b63b5 Doc Update 2026-02-15 01:09:37 -08:00
oleibman ba99bd148e Add Test 2026-02-02 20:16:35 -08:00
oleibman d63e6b1584 Reorganize 2 Tests 2026-02-02 12:40:56 -08:00
oleibman 70768bd5d2 Coverage Tweaks 2026-02-01 14:10:58 -08:00
oleibman 9bd828e25d Strange Behavior of CONCATENATE
Fix #4061. CONCATENATE, which has slightly different behavior than CONCAT, and which MS has deprecated for that reason, behaves in an unexpected way when a cell range is presented to it and the spreadsheet does not allow for array results. This would almost certainly occur only for Legacy spreadsheets, but such is what was presented in the issue. The code is changed so that when an array of cells is presented to CONCATENATE, and RETURN_ARRAY_AS_VALUE is in effect, the array will be treated as if it were wrapped in the SINGLE pseudo-function (which is what Excel does by somewhat mysteriously prefixing the cell range with `@`).

This is a niche case. This one stands out because of its deprecation and replacement function. It is possible that other functions exhibit this behavior. I have made no attempt to identify others. A similar approach can probably be applied if issues are raised for others.
2026-02-01 08:51:39 -08:00
oleibman f8ce6a9a83 Support for Defined Names 2026-01-20 12:19:23 -08:00
oleibman 3590000687 Merge branch 'master' into issue4656 2026-01-19 12:42:24 -08:00
oleibman 7f284c7d93 Deprecate FormulaParser, FormulaToken
Unused in this project. I checked to see if they might be of use for this PR. They aren't.
2026-01-19 12:38:56 -08:00
oleibman 860f2c6637 Restore Tests Whose Absence Led to Omitted Coverage 2026-01-16 18:21:57 -08:00
oleibman 501f4fe6ee Test CellRanges with Union 2026-01-15 12:19:25 -08:00
oleibman 23b12228fd Merge branch 'master' into issue4656 2026-01-14 19:59:49 -08:00