77 Commits

Author SHA1 Message Date
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 d7f59e225e T.INV Don't Use Locale-Aware sprintf
Uppercase f to F, add test.
2025-12-24 16:45:40 -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 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
oleibman f8f78a7ed3 String Increments and Php8.5
Fix #4600. String incrementation through the `++` operator is deprecated in Php 8.5. Because we make use of that operator to iterate through columns, we are particularly hard hit by that change - unaddressed, it causes over 2,000 errors in our test suite! It is, fortunately, not as difficult as I feared to correct. Replacing the `++` operator with a call to new method `StringHelper::stringIncrement` in 79 statements scattered over 31 source modules (in src, samples, test, and infra) eliminates all the messages in the test suite. It is possible that others are lurking, but I don't know a systematic way of determining if there are others. We'll stick with this for now, and deal with any others as they show up.

This PR will be applied to the master, release390, and release222 branches. It will not be applied to the release210 or release1291 branches, which will now accept security changes only.
2025-08-27 18:18:40 -07:00
oleibman 150b3849a8 Create Uninitialized Cell If Used in Calculation
Fix #4558. Fix #4530. 4558 deals with a problem with COUNTA. Excel does not count empty cells, but it does count literal nulls (usually empty parameters) in the argument list. PhpSpreadsheet has till now not created empty cells during calculation, and so winds up treating them as literals. It is changed to create the cell when appropriate.

4530 has a similar problem. When INDIRECT winds up evaluating an uninitialized cell, it treats it as a null literal, leading to incorrect results. It is now changed to create the missing cell.

A number of functions, almost all of them Financial, have a related problem. They test for a null literal when initializing some of their parameters, but they should be testing for null literal or cell containing null. They are changed to do the right thing.

SUMIF and related functions are slightly affected by this change, and are changed so that they are no longer affected.

New tests have been added. Only one existing test had to change. MergeBehaviorTest for `[12, '=5+1', '=A1/A2']`, where cell A2 was uninitialized formerly returned one row, but now returns a second all-null row because A2, by virtue of being used in a calculation, now winds up defined. I do not consider this a significant difference.
2025-07-31 23:58:05 -07:00
oleibman 5a9055ca6a Phpstan Level 10 (Final)
Fix all of Statistical. We can now use Level 10 going forward.
2025-05-22 17:48:30 -07:00
oleibman 7db6e1f491 More prep for Phpstan 10 2025-05-12 22:59:34 -07:00
oleibman e32447c4be Phpstan Level 9: Last 2025-03-31 21:53:47 -07:00
oleibman d1d12ed92d Phpstan Level 9 - Part 1 of Many
Dealing mostly with mixed variable type. In a great many cases, problems can be resolved replacing `getValue` with `getValueString` or `getCalculatedValue` with `getCalculatedValueString`.
2025-03-05 21:59:54 -08:00
oleibman db6e6ebd72 3rd Parameter for AVERAGEIF/SUMIF Can Also Be #REF! 2025-02-25 20:11:23 -08:00
oleibman 990e3ec3a3 Handle #REF! As Argument to COUNTIF, AVERAGEIF, SUMIF
Fix #4381. The report refers to COUNTIF, but AVERAGEIF and SUMIF, which are implemented in the same module, exhibit the same behavior. (There may be others, but, for now, I will just fix those 3.)

Most methods which implement Excel functions should accept mixed arguments, so that they won't throw exceptions when calculated. Of course, MS often doesn't give much guidance as to how unexpected arguments should be handled. It at least seems clear that MS will often substitute #REF! for some arguments, and will return #REF! as the result in such cases. My test indicates that a formula using, say, #DIV/0! in lieu of #REF! will cause Excel to deem the spreadsheet corrupt. So, I think I am just going to deal with #REF! and let other unexpected values continue to throw exceptions.
2025-02-24 21:40:12 -08:00
oleibman e637713de9 Make Explicit Array Return Type When Tests Require It
When the Dynamic Array PR #3962 was introduced, it left the default as Return Array as Value. At some point, the default should be changed to Return Array as Array. This would, of course, be a breaking change, one which will not be part of Release 4. However, it will possibly be part of Release 5.

Rather than relying on the default setting, this PR explicitly sets Return Array as Value when tests require that setting. This will make it easier to identify potential breaks when the default is changed. The entire test suite will now succeed with either setting as default.

In making these changes, a few minor problems were discovered with how Array as Array is handled. These are fixed with this PR.
2025-01-25 19:40:12 -08:00
oleibman d647fe7ee7 Use Php Attributes Rather than Annotations for PhpUnit
With PhpUnit 10 came the ability to use Php attributes rather than doc-block annotations for things like "data provider". PhpUnit 11 deprecates the use of annotations, and PhpUnit 12 will not not permit their use. Since PhpUnit 11 requires Php8.2+, we cannot adopt it as long as we support Php8.1, which will continue to be the case for some time. However, there is no penalty for early adoption.

Php-cs-fixer can use:
```
'php_unit_attributes' => ['keep_annotations' => false],
```
This allows us to run `composer fix` to automate all the needed changes. No manual changes were needed for any of the test members.

With this change, PhpUnit 9 can no longer be used with the test suite. File composer.json is updated to reflect that reality, and phpunit9.xml.dist, which has been supplied in case anyone needed to use PhpUnit 9, is no longer required, and is thus deleted. For now, PhpUnit 11 is not being added as a possibility.

No source code is changed in this PR.
2024-11-23 20:56:26 -08:00
oleibman bbf9d15cb2 Php-cs-fixer Enforcing New Rules
The latest release seems to not want you to give a class element both a Php type and a doc-block type. I used the "fix" operand to delete the redundant doc-block declarations, with no other changes. So there should be no change to executable code.
2024-08-01 10:54:54 -07:00
oleibman 3eedf9e2f0 Better Typing in Test Members
Change "mixed" declarations to more accurate types in test members; in particular, change those that would be flagged if we were to run Phpstan at level 9 (we currently run level 8). I may or may not follow up with source code (over 700 level-9 problems remain for src), but, as with strict typing, there is no reason to avoid the effort for test members.

It was necessary to update some doc blocks in src to accommodate this change. However, no executable code is touched.
2024-01-05 01:43:50 -08:00
Adrien Crivelli 816b91d0b4 BREAKING Drop all deprecated things 2023-12-10 19:04:19 +01:00
Adrien Crivelli 959719a4d3 Apply PhpCsFixer phpdoc_to_param_type 2023-12-10 10:36:43 +01:00
Adrien Crivelli 77e6cc528c Apply PhpCsFixer phpdoc_to_property_type 2023-12-10 09:16:38 +01:00
Adrien Crivelli cbffcbcbbc Drop some tricks used for Scrutinizer false positives 2023-09-21 11:14:47 +08:00
Adrien Crivelli 2a0a53cd6a Drop all Scrutinizer annotations
Because they tend to clutter our code a lot and unfortunately, there are
lots of false positives. Instead, it would probably be better to deal
with false positives out of band, via the Scrutinizer web UI.
2023-09-21 11:14:47 +08:00
Adrien Crivelli b881309496 Rector StrContainsRector, StrStartsWithRector, StrEndsWithRector 2023-09-13 10:06:58 +08:00
Adrien Crivelli 80b4ae2bbd Rector TypedPropertyFromStrictSetUpRector 2023-09-12 10:48:13 +08:00
Adrien Crivelli 4b0da60ec4 Rector ParamTypeByMethodCallTypeRector 2023-09-08 00:18:38 +08:00
Adrien Crivelli ec4098c8fd Strict mode for all tests
While we might never be able to have 100% of our code strict, we can at
the very least do it for all of our tests. This ensures that our tests
are using our API with the types as intended by the test author, and not
silently be cast to what our API requires.
2023-09-07 17:44:56 +08:00
Adrien Crivelli 1b05dfab8b Rector AddParamTypeBasedOnPHPUnitDataProviderRector
And quite a bit more manual changes. The idea is that typing of our
tests can be a bit more loose, so we assume PHPDoc is mostly correct. If
that happens to be wrong, it should be caught by the tests themselves.
2023-09-07 17:00:24 +08:00
oleibman e9cf27354d PhpUnit 10 Compatibility Part 2 (#3526)
Successor to PR #3523. There are 494 single-line changes (`public function provider` to `public static function provider`) in this PR. None of these were made manually; they were all created with the following script (adapted from
https://stackoverflow.com/questions/25909820/how-to-recursively-iterate-through-files-in-php):
```php
$dir = 'C:/git/unit10prep2/tests/PhpSpreadsheetTests';
$it = new RecursiveDirectoryIterator($dir);

// Loop through files
foreach(new RecursiveIteratorIterator($it) as $file) {
    if ($file->getExtension() === 'php') {
        $contents = file_get_contents($file);
        $new = preg_replace('/public function (\\w*)([Pp])rovider/', 'public static function $1$2rovider', $contents);
        if ($new !== $contents) {
            echo "changing $file\n";
            file_put_contents($file, $new);
        }
    }
}
```

After this PR, there will be one more, with a small number of test changes, and enabling PhpUnit 10 for Php 8.1+.
2023-04-20 13:48:00 -07:00
oleibman 8497a320ea Resolve Phpstan Messages in Calculation Statistical (#3290)
* Resolve Phpstan Messages in Calculation Statistical

Reduce number of Phpstan messages by addressing their issues.

I did not eliminate the messages for Trends, because I did not understand why it is working as it does, so felt it was better to leave it alone.

* Scrutinizer

A new false positive.
2023-01-16 07:19:39 -08:00
oleibman 4adafecea9 Eliminate Shared\JAMA (#3260)
* Eliminate Shared\JAMA

The code under `Shared\JAMA` is called from exactly 3 places in Calculation, and exactly 1 other place (see next paragraph). These places are inadequately covered in the test suite, so it is not clear that the existing code works. In addition, see PR #2964 for commentary decrying the continued used of JAMA. I will also note that it has a pitiful 8.20% coverage in the test suite. There seems to already be a perfectly adequate equivalent in Calculation for those parts of JAMA which are used (e.g. we don't use any decompositions, so the fact that no decomposition code is present in Calculation is not a problem). This PR replaces the uses of JAMA within Calculation with `checkMatrixOperands`, and deletes Shared/JAMA entirely (which, among other thing, makes many Phpstan reported problems go away). The test suite is enhanced to ensure coverage of all the changed statements. A side benefit of deleting Shared/Jama is that the only function which PhpSpreadsheet has added to the global namespace (hypo in Maths.php) goes away.

There is one additional use in Shared/Trend/PolynomialBestFit. That use is best replaced with Matrix/Matrix, which is already a requirement for PhpSpreadsheet. PolynomialBestFit has zero test coverage, and I didn't add any for this change. There seem to be existing errors which both Phpstan and Scrutinizer complain about, and I am quite convinced that they are not false positives. Fixing those problems will be a project for another day.

Calculation has many calls to `Information\ErrorValue` and `Information\ExcelError`. It is inconsistent in how it does so - most invoke `Information\E...` but a small number take advantage of additional `use` statements to just invoke `E...`. I have made the usage consistent by changing the small number to act like the majority and eliminating the `use` statements.

Some of the test cases exposed the fact that MAX, MAXA, MIN, and MINA do not properly handle error strings in their input. For example, if cell A1 contains 2, and A2 contains `=5/0`, `=MAX(A1, A2)` should return `#DIV/0!`. They are changed to handle them properly.

`Shared\JAMA` was normally omitted from code coverage. Since it is being deleted, there is no longer a reason to explicitly exclude it. `Writer\PDF` was also on the exclude list, probably for historical reasons, but there is no reason to exclude it now (it is, in fact, 100% covered), so it will no longer be excluded.

* Scrutinizer

Eliminate some newly dead code.
2022-12-30 07:22:58 -08:00
oleibman 057572ee90 Change Additional Statistical Tests to Use Spreadsheet Context (#3217)
* Change Additional Statistical Tests to Use Spreadsheet Context

With an earlier change, I made all but 18 Statistical tests run in spreadsheet context. This PR changes 12 of those 18. The remaining 6 usually return array results, so it is a tougher task to handle them. I will continue to think on it.

AVERAGEIF, AVERAGEIFS, and COUNTBLANK are changed to throw an Exception when a range is specified as a literal. They previously accepted array (enclosed in braces) literals, and bumbled along till they threw an error for non-array literals. Throwing an exception appears to be analogous to how Excel operates, rather than something more friendly like a VALUE error. There may be other functions which require similar treatment.

There also remains a TODO for COUNTIFS, and possibly other functions. It appears that PhpSpreadsheet counts booleans for both integer and string compares and probably shouldn't. Again, this is a problem for another day.

* Scrutinizer

Fix one problem.

* Scrutinizer Ignores Its Own Suggested Remedy

Try another approach.
2022-12-02 14:14:43 -08:00
oleibman d2deb133bc Fix Unintential Deprecated Calls in Tests - STATISTICAL (#3181)
* Fix Unintential Deprecated Calls in Tests - STATISTICAL

I think it's best to install these before PR #3166. There are no changes to source code, only to doc-blocks and to test members which continue to inadvertently use calls to deprecated functions.

* Missed One Deprecation

Fix it now.

* Run Tests in Spreadsheet Context

This is quite a bit more difficult for Statistical than for the other Calculation categories. This is partly because of the use of multi-dimensional matrices, and also because some arguments are interpreted differently when they come from a cell rather than entered directly in a formula. This push leaves 18 out of 89 test members unchanged, except that they are marked with a TODO to show that the work isn't finished. I will not revisit them as part of this PR, but probably will take a look in a subsequent ticket.
2022-11-25 13:57:17 -08:00
oleibman c112802023 Eliminate Most Scrutinizer Problems in Test Suite (#2699)
* Eliminate Most Scrutinizer Problems in Test Suite

Mostly minor code changes, with some annotations.

* Missed 2 php-cs-fixer Problems

They should be fixed now.
2022-03-21 13:58:42 -07:00
Mark Baker 35b65bef8c First steps toward array-enabling the information functions (#2608)
* First steps toward array-enabling the information functions

Also includes moving unit tests out from Functions and into a separate, dedicated Information folder

* Resolve issue with IF(), branch pruning and calculation cache (ensure that we don't convert the if condition to a bool before we've tested to see if it evaluates to an error)
More refactoring
2022-02-20 16:46:25 +01:00
Mark Baker cdbe5c7c9b Converting Statistical functions to be array-enabled (phase #2) (#2593) 2022-02-15 20:34:39 +01:00
Mark Baker f1cb75eaec Start work on Array-enabling the Statistical functions (#2588) 2022-02-15 14:41:17 +01:00
oleibman 1e74282259 Fix for Issue 2158 (AverageIf Calculation Problem) (#2160)
* Improve Identification of Samples in Coverage Report

The Phpunit coverage report currently contains bullet items like `PhpOffice\PhpSpreadsheetTests\Helper\SampleTest\testSample with data set "49"`. This extremely simple change takes advantage of Phpunit's ability to accept an array with keys which are either strings or integers, by using the sample filenames as the array keys rather than sequential but otherwise meaningless integers (e.g. `49` in the earlier cited item). The bullet item will now read `PhpOffice\PhpSpreadsheetTests\Helper\SampleTest\testSample with data set "Basic/38_Clone_worksheet.php"`.

* Fix for Issue 2158 (AverageIf Calculation Problem)

Issue #2158 reports an error calculating AverageIf because a function returns null rather than a string. There turn out to be several components to this problem:
- The nominal fix to the problem is to add some null-to-nullstring coercion in DatabaseAbstract.
- This fixes the error, but does not necessarily lead to the correct result because buildQuery treats values of null and null-string identically, whereas Excel does not. So change that to treat null-string as any other string.
- But that doesn't lead to the correct result either. That's because Functions/ifCondition recognizes a null string, but then continues to (over-)process it until it returns the wrong result. Fix this problem in conjunction with the other two, and we finally get the correct result.

A new unit test is added for AVERAGEIF, and new test cases are added for SUMIF. In each case, there are complementary tests for conditions of null and null-string, and the results agree with Excel. There may or may not be value in adding new tests to other functions, and I will be glad to do so for any functions which you care to identify, but no existing tests broke as a result of these changes.
2021-06-15 09:54:57 +02:00
Mark Baker e4973fa041 Start work on refactoring the last of the Excel Statistical functions (#2033)
* Refactoring the last of the Excel Statistical functions
2021-04-29 14:34:50 +02:00
Adrien Crivelli 49f87de165 Reduce PHPStan error in tests 2021-04-12 11:10:23 +09:00
Mark Baker dd74dd7fcf Let's start with some appeasements to phpstan, just to reduce the baseline (#1983)
* Let's start with some appeasements to phpstan, just to reduce the baseline
* Appeasements to phpstan, taking the number of reported errors down to just 61
2021-04-03 17:10:40 +02:00
Mark Baker a2bb825bc5 Extract Normal and Standard Normal Distributions from the Statistical Class (#1981)
* Extract Normal and Standard Normal Distributions from the Statistical Class
* Extract ZTest from the Statistical Class, and move it to the Standard Normal Distribution class
Additional unit tests for NORMINV()
* Extract LogNormal distribution functions from Statistical
2021-04-02 20:17:03 +02:00
Mark Baker 17af13281b Extract a few more Distribution functions from Statistical (#1975)
* Extract a few more Distribution functions from Statistical; this time EXPONDIST() and HYPGEOMDIST()

* Extract the F Distribution (although only F.DIST() is implemented so far

* Updae docblocks

* PHPCS
2021-03-31 21:45:06 +02:00
Mark Baker 029f345987 Extract Binomial Distribution functions from Statistical (#1974)
* Extract Binomial Distribution functions from Statistical
Replace the old MS algorithm for CRITBINOM() (which has now been replaced with te BINOM.INV() function) with a brute force approach - I'll look to refine it later. The MS algorithm is no longer documented, and the implementation produced erroneous results anyway

* Exract the NEGBINOMDIST() function as well; still need to add a cumulative flag to support the additional argument for the newer NEGBINOM.DIST() function
* Rationalise validation of probability arguments
2021-03-30 22:49:10 +02:00