mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-08-31 04:28:51 +00:00
52da37f4a7
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).
121 lines
5.4 KiB
PHP
121 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
|
|
|
class GammaInvTest extends AllSetupTeardown
|
|
{
|
|
private function gammaInvFormulaResult(float $probability, float $alpha, float $beta): mixed
|
|
{
|
|
$sheet = $this->getSheet();
|
|
$sheet->getCell('A1')->setValue($probability);
|
|
$sheet->getCell('A2')->setValue($alpha);
|
|
$sheet->getCell('A3')->setValue($beta);
|
|
$sheet->getCell('B1')->setValue('=GAMMA.INV(A1, A2, A3)');
|
|
|
|
return $sheet->getCell('B1')->getCalculatedValue();
|
|
}
|
|
|
|
/**
|
|
* Upper-tail quantiles whose true root exceeds the old fixed alpha*beta*5
|
|
* bracket ceiling that used to clamp the result to it. Reference values from
|
|
* mpmath findroot on the regularized gammainc.
|
|
*/
|
|
#[\PHPUnit\Framework\Attributes\DataProvider('providerGammaInvExtremeTail')]
|
|
public function testGammaInvExtremeTail(float $expected, float $probability, float $alpha, float $beta): void
|
|
{
|
|
self::assertEqualsWithDelta($expected, $this->gammaInvFormulaResult($probability, $alpha, $beta), 1.0e-3);
|
|
}
|
|
|
|
public static function providerGammaInvExtremeTail(): array
|
|
{
|
|
return [
|
|
'p=0.9999 alpha=1 beta=1' => [9.210340371976, 0.9999, 1.0, 1.0],
|
|
'p=0.99995 alpha=1 beta=1' => [9.903487552536, 0.99995, 1.0, 1.0],
|
|
'p=0.9999 alpha=0.5 beta=2' => [15.136705226623, 0.9999, 0.5, 2.0],
|
|
'p=0.9999 alpha=1 beta=2' => [18.420680743952, 0.9999, 1.0, 2.0],
|
|
// Past #4945, regularizedGammaP/Q are accurate at this depth, so the
|
|
// bracket now reaches the true root instead of the old alpha*beta*5
|
|
// ceiling; -ln(1e-7) is the closed-form check (alpha=1).
|
|
'p=0.9999999 alpha=1 beta=1' => [16.118095650958, 0.9999999, 1.0, 1.0],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Shape parameters whose pdf/cdf used to overflow or under-iterate: alpha in
|
|
* ~[143, 171.62] silently returned the first bisection midpoint (alpha=143
|
|
* gave 358.0), larger alpha gave #NUM!, and alpha above ~5000 drifted from
|
|
* the true root. References from mpmath 50-digit findroot on regularized P.
|
|
*/
|
|
#[\PHPUnit\Framework\Attributes\DataProvider('providerGammaInvLargeShape')]
|
|
public function testGammaInvLargeShape(float $expected, float $probability, float $alpha, float $beta): void
|
|
{
|
|
self::assertEqualsWithDelta($expected, $this->gammaInvFormulaResult($probability, $alpha, $beta), 1.0e-6);
|
|
}
|
|
|
|
public static function providerGammaInvLargeShape(): array
|
|
{
|
|
return [
|
|
'p=0.001 alpha=143' => [108.87480324556955, 0.001, 143.0, 1.0],
|
|
'p=0.5 alpha=143' => [142.66680515301345, 0.5, 143.0, 1.0],
|
|
'p=0.9999 alpha=143' => [191.80362061926086, 0.9999, 143.0, 1.0],
|
|
'p=0.001 alpha=171.7' => [134.03805036756231, 0.001, 171.7, 1.0],
|
|
'p=0.5 alpha=171.7' => [171.36678195559252, 0.5, 171.7, 1.0],
|
|
'p=0.9999 alpha=171.7' => [224.75826597753954, 0.9999, 171.7, 1.0],
|
|
'p=0.5 alpha=172' => [171.66678175408084, 0.5, 172.0, 1.0],
|
|
'p=0.001 alpha=200' => [159.12980117448907, 0.001, 200.0, 1.0],
|
|
'p=0.5 alpha=200' => [199.66676561246567, 0.5, 200.0, 1.0],
|
|
'p=0.9999 alpha=200' => [256.91788180053889, 0.9999, 200.0, 1.0],
|
|
'p=0.001 alpha=1000' => [905.12079093497662, 0.001, 1000.0, 1.0],
|
|
'p=0.5 alpha=1000' => [999.66668642696518, 0.5, 1000.0, 1.0],
|
|
'p=0.9999 alpha=1000' => [1121.9041774983375, 0.9999, 1000.0, 1.0],
|
|
'p=0.5 alpha=200 beta=2' => [399.33353122493135, 0.5, 200.0, 2.0],
|
|
'p=0.9999 alpha=500 beta=3' => [1762.4002448762386, 0.9999, 500.0, 3.0],
|
|
'p=0.5 alpha=10000' => [9999.666668642047, 0.5, 10000.0, 1.0],
|
|
'p=0.001 alpha=100000' => [99025.63189050092, 0.001, 100000.0, 1.0],
|
|
'p=0.5 alpha=100000' => [99999.6666668642, 0.5, 100000.0, 1.0],
|
|
'p=0.9999 alpha=100000' => [101180.33552637429, 0.9999, 100000.0, 1.0],
|
|
];
|
|
}
|
|
|
|
#[\PHPUnit\Framework\Attributes\DataProvider('providerGAMMAINV')]
|
|
public function testGAMMAINV(mixed $expectedResult, mixed ...$args): void
|
|
{
|
|
$this->runTestCases('GAMMA.INV', $expectedResult, ...$args);
|
|
}
|
|
|
|
public static function providerGAMMAINV(): array
|
|
{
|
|
return require 'tests/data/Calculation/Statistical/GAMMAINV.php';
|
|
}
|
|
|
|
#[\PHPUnit\Framework\Attributes\DataProvider('providerGammaInvArray')]
|
|
public function testGammaInvArray(array $expectedResult, string $values, string $alpha, string $beta): void
|
|
{
|
|
$calculation = Calculation::getInstance();
|
|
|
|
$formula = "=GAMMA.INV({$values}, {$alpha}, {$beta})";
|
|
$result = $calculation->calculateFormula($formula);
|
|
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
|
|
}
|
|
|
|
public static function providerGammaInvArray(): array
|
|
{
|
|
return [
|
|
'row/column vectors' => [
|
|
[
|
|
[2.772588722239782, 5.38526905777939, 12.548861396889375],
|
|
[5.545177444479561, 10.770538115558788, 25.097722793778765],
|
|
[6.931471805599456, 13.463172644448484, 31.372153492223447],
|
|
],
|
|
'0.75',
|
|
'{1, 2, 5}',
|
|
'{2; 4; 5}',
|
|
],
|
|
];
|
|
}
|
|
}
|