325 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
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
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
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
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
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 4a476071b3 Merge pull request #4850 from oleibman/updatestan
Update PhpStan
2026-04-05 07:15:00 +00: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
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 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 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 391a0a7f31 SUBTOTAL and Hidden Rows
Fix #820, which went stale in 2019 and is now reopened. Another weird Excel implementation detail. When SUBTOTAL's first argument is 101 through 111, it excludes all hidden rows. When that argument is 1 through 11, it excludes all rows which are hidden due to an auto-filter, but includes rows which were manually hidden. PhpSpreadsheet had not been distinguishing between the two types of "hidden-ness". Add a new property to RowDimension to indicate whether it is hidden due to an auto-filter, and use that property when evaluating SUBTOTAL 1-11.
2025-12-30 15:10:50 -08:00
oleibman 31174c1c90 Set All Locale Variables at Once in a Threadsafe Manner
Fix #954, which went stale 6 years ago and which I have now reopened. The `setLocale` method in `Calculation` and `Settings` does not affect the 3 localizable properties in `StringHelper` - `currencyCode`, `decimalSeparator`, and `thousandsSeparator`. One way to work around this problem is to set those properties separately; this is probably how most people handle it. Another way is to call Php's own `setlocale` function; this does not require any foreknowledge of what the values need to be, but it comes with baggage (data is maintained at process level rather than thread level), so its use is discouraged.

This PR adds a new `setLocale` method to `StringHelper`. It sets the 3 properties and the `Calculation` language all at once. It depends on the `Intl` extension, which is a recommendation but not a formal requirement for PhpSpreadsheet; the method will return `false` if `Intl` is not available or it thinks the supplied locale is not valid.
2025-12-25 19:24:44 -08:00
oleibman d7f59e225e T.INV Don't Use Locale-Aware sprintf
Uppercase f to F, add test.
2025-12-24 16:45:40 -08:00
oleibman 9bc3a73ba4 Make White List Instance Property Rather than Static 2025-12-21 22:31:45 -08:00
oleibman 2ce3ac1a90 Wrong Case for Directory Name 2025-12-19 10:24:21 -08:00
oleibman 84747121a1 Changes to WEBSERVICE
This PR started because the documentation on how to configure an Http client was out of date. However, as I investigated further, I found problems. GuzzleHttp, a client mentioned in the documentation, for example - it probably works fine when executed from a browser, but it does not work for `https:` requests from the Windows command line. See https://github.com/guzzle/guzzle/issues/2601, where a user notes that Guzzle's own program to demonstrate how to use it doesn't work, a result that I can confirm is still true. A linked explanation says that the solution is to explicitly specify a path to a CA bundle. For starters, this is impractical from our perspective. One possible solution is to change a php.ini option which is not needed for any other purpose, and which probably needs to change frequently - a burden on users who follow that route. An alternative solution is to use a method `request` to specify the path to the certificate store; this also will need to change from time to time, and, worse, the only method defined in ClientInterface is `sendRequest`, so using this solution isn't client-agnostic, which is a stated goal of https://github.com/PHPOffice/PhpSpreadsheet/commit/7cb4884b96174eb611621635e6fb603ef54178f7. Additionally, it is not clear why an external interface is needed rather than a call to file_get_contents, used elsewhere in PhpSpreadsheet, and not requiring a path to a certificate store.

I also believe that automatically evaluating WEBSERVICE for any arbitrary argument is not a good idea. I am adding a domain whitelist which the user must populate. For domains not in the whitelist, the calculation will revert to `oldCalculatedValue`, which is good enough for pass-through purposes, which probably encompasses most cases. That is how Excel behaves by default - it disables WEBSERVICE calls when it opens a spreadsheet which contains them. For cases where the user adds a new WEBSERVICE call, there is a choice of whitelisting the domain, or getting the result in some other way and using `setCalculatedValue` to store it.

Finally, when a WEBSERVICE call *is* evaluated, it will now accept a cell-address argument rather than just a literal string as is now the case.
2025-12-19 10:12:50 -08:00
oleibman 591ef9acd9 Implement Rest of T.DIST
Found an algorithm for T.DIST  when third argument is FALSE.
2025-12-16 21:34:59 -08:00
oleibman 5b4d7fcdc8 Partial Implementation of T.DIST
T.DIST takes 3 parameters. When the 3rd parameter, a boolean, is true, we now have code to support the request. We still do not have code to handle the false case.
2025-12-16 17:10:53 -08:00
oleibman 5eb46bd48e Fix Some Functions Related to Student-T Distribution
Fix #4167. That problem was initially reported as a problem with TINV, however, at its root, it was actually a problem with TDIST (which TINV needs to call). Both of these are deprecated by Microsoft in favor of some newer functions. PhpSpreadsheet incorrectly treated T.INV as an alias for TINV, and did not implement T.INV.2T, which actually is an alias for TINV.

To solve this problem, I adapted the Perl CPAN module `Statistics::Disttributions`, written by Michael Kospach around 2003. Unit test results have been manually checked against Excel; a few had to be changed because they were wrong (see the original issue). I believe that all of TDIST, T.DIST.2T, T.DIST.RT, TINV, T.INV, and T.INV.2T now function correctly, at least most of the time. I have not found code or an algorithm that I can use for T.DIST nor for T.TEST, both of which remain unimplemented.
2025-12-15 16:53:30 -08:00
oleibman f62034a752 Booleans
I don't know how Excel managed to mess up sorts involving booleans, but they did.
2025-12-10 14:57:02 -08:00
oleibman 9362d21bb5 Additional Test 2025-12-10 11:47:11 -08:00
oleibman 559ab2294a Better Tests for SORT and SORTBY
Current tests use only direct calls. Using them in spreadsheet context would be better.

There were some minor bugs. Excel treats an argument consisting of a single-cell as if it were a range; PhpSpreadsheet, which currently returns the content of the single cell, is changed to match that behavior, which is important if one of the other arguments causes a VALUE error.

Excel can do a column sort in SORTBY, despite the fact that Microsoft doesn't even mention that possibility in its documentation. I found documentation of that possibility on a 3rd-part site https://exceljet.net/functions/sortby-function, confirmed it in Excel, and took my best guess as to how to implement it. The existing test member for SORTBY included one such test, but it succeeds mostly by accident. The new member has a more reliable test.
2025-12-09 23:13:12 -08:00
oleibman edd28628f3 Typos in Test Comments 2025-12-01 11:00:23 -08:00
oleibman 5c504ac4ce MACROMAN Charset is on My System, but not Github's - Try MAC Instead 2025-11-28 18:15:10 -08:00
oleibman b243f2f4e5 CODE/UNICODE and CHAR/UNICHAR
PhpSpreadsheet treats CODE and UNICODE as equivalent, likewise for CHAR and UNICHAR. They are, in fact, different. CODE and CHAR deal only with single-byte character sets (Windows-1252 or MacRoman), while UNICODE and UNICHAR deal with all of Unicode. This PR separates them. The existing unit test for CODE was, in many cases, applicable to UNICODE (for which there was no separate test). The tests are corrected for CODE, new tests are added, and a separate test for UNICODE is added. CHAR was mostly okay, new tests are added, and a separate test for UNICHAR is added.
2025-11-28 13:21:27 -08:00
Robin van der Vliet 122b36b09f Implement missing BAHTTEXT function 2025-11-16 18:02:10 +01:00
oleibman dcf475eec1 Merge pull request #4713 from oleibman/calcvalue
Use `calculateFormula` Rather Than `_calculateFormulaValue` in Tests
2025-11-14 05:37:48 +00:00
oleibman 55098edf99 Minor Touchup 2025-11-13 21:07:19 -08:00
oleibman 70b4ecd4d3 Use calculateFormula Rather Than _calculateFormulaValue in Tests
They aren't quite interchangeable. Both are used in the test suite, with no indication of why one or the other. I think we'd be best off being consistent. Based on the names, I think `_calculateFormulaValue` was intended as a private, or at least internal, method, so favor `calculateFormula`. I do not intend to rename or re-categorize `_calculateFormulaValue`, just remove its usage when it isn't clearly warranted.
2025-11-13 20:48:46 -08:00
Robin van der Vliet 70802fafe6 Add another test 2025-11-13 20:43:41 +01:00
Robin van der Vliet e929a94ef6 Fix tests 2025-11-11 23:17:46 +01:00
Robin van der Vliet e18bf2a2ef Fix tests 2025-11-10 22:45:43 +01:00
Robin van der Vliet e9ec166018 Add tests 2025-11-10 21:50:15 +01:00
oleibman c944b1a162 Merge pull request #4697 from oleibman/issue4696
Unexpected Exception in Php DateTime
2025-11-02 07:02:47 +00:00
oleibman 938ba72eb1 Coverage Tweak 2025-11-01 23:39:14 -07:00