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
This commit is contained in:
Mark Baker 2021-03-30 22:49:10 +02:00 committed by GitHub
parent df93577ef8
commit 029f345987
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 484 additions and 194 deletions

View File

@ -418,22 +418,22 @@ class Calculation
], ],
'BINOMDIST' => [ 'BINOMDIST' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'BINOMDIST'], 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
'argumentCount' => '4', 'argumentCount' => '4',
], ],
'BINOM.DIST' => [ 'BINOM.DIST' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'BINOMDIST'], 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
'argumentCount' => '4', 'argumentCount' => '4',
], ],
'BINOM.DIST.RANGE' => [ 'BINOM.DIST.RANGE' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'], 'functionCall' => [Statistical\Distributions\Binomial::class, 'range'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'BINOM.INV' => [ 'BINOM.INV' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'], 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'BITAND' => [ 'BITAND' => [
@ -695,7 +695,7 @@ class Calculation
], ],
'CRITBINOM' => [ 'CRITBINOM' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'CRITBINOM'], 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'CSC' => [ 'CSC' => [
@ -1751,7 +1751,7 @@ class Calculation
], ],
'NEGBINOMDIST' => [ 'NEGBINOMDIST' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'NEGBINOMDIST'], 'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'NEGBINOM.DIST' => [ 'NEGBINOM.DIST' => [

View File

@ -0,0 +1,7 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
class Single
{
}

View File

@ -251,6 +251,11 @@ class Statistical
* experiment. For example, BINOMDIST can calculate the probability that two of the next three * experiment. For example, BINOMDIST can calculate the probability that two of the next three
* babies born are male. * babies born are male.
* *
* @Deprecated 1.18.0
*
* @see Statistical\Distributions\Binomial::distribution()
* Use the distribution() method in the Statistical\Distributions\Binomial class instead
*
* @param mixed (float) $value Number of successes in trials * @param mixed (float) $value Number of successes in trials
* @param mixed (float) $trials Number of trials * @param mixed (float) $trials Number of trials
* @param mixed (float) $probability Probability of success on each trial * @param mixed (float) $probability Probability of success on each trial
@ -260,34 +265,7 @@ class Statistical
*/ */
public static function BINOMDIST($value, $trials, $probability, $cumulative) public static function BINOMDIST($value, $trials, $probability, $cumulative)
{ {
$value = Functions::flattenSingleValue($value); return Statistical\Distributions\Binomial::distribution($value, $trials, $probability, $cumulative);
$trials = Functions::flattenSingleValue($trials);
$probability = Functions::flattenSingleValue($probability);
if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) {
$value = floor($value);
$trials = floor($trials);
if (($value < 0) || ($value > $trials)) {
return Functions::NAN();
}
if (($probability < 0) || ($probability > 1)) {
return Functions::NAN();
}
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
if ($cumulative) {
$summer = 0;
for ($i = 0; $i <= $value; ++$i) {
$summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i);
}
return $summer;
}
return MathTrig::COMBIN($trials, $value) * $probability ** $value * (1 - $probability) ** ($trials - $value);
}
}
return Functions::VALUE();
} }
/** /**
@ -510,6 +488,11 @@ class Statistical
* *
* See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used * See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used
* *
* @Deprecated 1.18.0
*
* @see Statistical\Distributions\Binomial::inverse()
* Use the inverse() method in the Statistical\Distributions\Binomial class instead
*
* @param float $trials number of Bernoulli trials * @param float $trials number of Bernoulli trials
* @param float $probability probability of a success on each trial * @param float $probability probability of a success on each trial
* @param float $alpha criterion value * @param float $alpha criterion value
@ -523,110 +506,7 @@ class Statistical
*/ */
public static function CRITBINOM($trials, $probability, $alpha) public static function CRITBINOM($trials, $probability, $alpha)
{ {
$trials = floor(Functions::flattenSingleValue($trials)); return Statistical\Distributions\Binomial::inverse($trials, $probability, $alpha);
$probability = Functions::flattenSingleValue($probability);
$alpha = Functions::flattenSingleValue($alpha);
if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) {
$trials = (int) $trials;
if ($trials < 0) {
return Functions::NAN();
} elseif (($probability < 0.0) || ($probability > 1.0)) {
return Functions::NAN();
} elseif (($alpha < 0.0) || ($alpha > 1.0)) {
return Functions::NAN();
}
if ($alpha <= 0.5) {
$t = sqrt(log(1 / ($alpha * $alpha)));
$trialsApprox = 0 - ($t + (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t));
} else {
$t = sqrt(log(1 / (1 - $alpha) ** 2));
$trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t);
}
$Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability)));
if ($Guess < 0) {
$Guess = 0;
} elseif ($Guess > $trials) {
$Guess = $trials;
}
$TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0;
$EssentiallyZero = 10e-12;
$m = floor($trials * $probability);
++$TotalUnscaledProbability;
if ($m == $Guess) {
++$UnscaledPGuess;
}
if ($m <= $Guess) {
++$UnscaledCumPGuess;
}
$PreviousValue = 1;
$Done = false;
$k = $m + 1;
while ((!$Done) && ($k <= $trials)) {
$CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability));
$TotalUnscaledProbability += $CurrentValue;
if ($k == $Guess) {
$UnscaledPGuess += $CurrentValue;
}
if ($k <= $Guess) {
$UnscaledCumPGuess += $CurrentValue;
}
if ($CurrentValue <= $EssentiallyZero) {
$Done = true;
}
$PreviousValue = $CurrentValue;
++$k;
}
$PreviousValue = 1;
$Done = false;
$k = $m - 1;
while ((!$Done) && ($k >= 0)) {
$CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability);
$TotalUnscaledProbability += $CurrentValue;
if ($k == $Guess) {
$UnscaledPGuess += $CurrentValue;
}
if ($k <= $Guess) {
$UnscaledCumPGuess += $CurrentValue;
}
if ($CurrentValue <= $EssentiallyZero) {
$Done = true;
}
$PreviousValue = $CurrentValue;
--$k;
}
$PGuess = $UnscaledPGuess / $TotalUnscaledProbability;
$CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability;
$CumPGuessMinus1 = $CumPGuess - 1;
while (true) {
if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) {
return $Guess;
} elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) {
$PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability);
$CumPGuessMinus1 = $CumPGuess;
$CumPGuess = $CumPGuess + $PGuessPlus1;
$PGuess = $PGuessPlus1;
++$Guess;
} elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) {
$PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability;
$CumPGuess = $CumPGuessMinus1;
$CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess;
$PGuess = $PGuessMinus1;
--$Guess;
}
}
}
return Functions::VALUE();
} }
/** /**
@ -1502,6 +1382,11 @@ class Statistical
* distribution, except that the number of successes is fixed, and the number of trials is * distribution, except that the number of successes is fixed, and the number of trials is
* variable. Like the binomial, trials are assumed to be independent. * variable. Like the binomial, trials are assumed to be independent.
* *
* @Deprecated 1.18.0
*
* @see Statistical\Distributions\Binomial::negative::mode()
* Use the negative() method in the Statistical\Distributions\Binomial class instead
*
* @param mixed (float) $failures Number of Failures * @param mixed (float) $failures Number of Failures
* @param mixed (float) $successes Threshold number of Successes * @param mixed (float) $successes Threshold number of Successes
* @param mixed (float) $probability Probability of success on each trial * @param mixed (float) $probability Probability of success on each trial
@ -1510,26 +1395,7 @@ class Statistical
*/ */
public static function NEGBINOMDIST($failures, $successes, $probability) public static function NEGBINOMDIST($failures, $successes, $probability)
{ {
$failures = floor(Functions::flattenSingleValue($failures)); return Statistical\Distributions\Binomial::negative($failures, $successes, $probability);
$successes = floor(Functions::flattenSingleValue($successes));
$probability = Functions::flattenSingleValue($probability);
if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) {
if (($failures < 0) || ($successes < 1)) {
return Functions::NAN();
} elseif (($probability < 0) || ($probability > 1)) {
return Functions::NAN();
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
if (($failures + $successes - 1) <= 0) {
return Functions::NAN();
}
}
return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) * ($probability ** $successes) * ((1 - $probability) ** $failures);
}
return Functions::VALUE();
} }
/** /**

View File

@ -33,4 +33,15 @@ trait BaseValidations
return (bool) $value; return (bool) $value;
} }
protected static function validateProbability($probability)
{
$probability = self::validateFloat($probability);
if ($probability < 0.0 || $probability > 1.0) {
throw new Exception(Functions::NAN());
}
return $probability;
}
} }

View File

@ -83,7 +83,7 @@ class Beta
$rMax = Functions::flattenSingleValue($rMax); $rMax = Functions::flattenSingleValue($rMax);
try { try {
$probability = self::validateFloat($probability); $probability = self::validateProbability($probability);
$alpha = self::validateFloat($alpha); $alpha = self::validateFloat($alpha);
$beta = self::validateFloat($beta); $beta = self::validateFloat($beta);
$rMax = self::validateFloat($rMax); $rMax = self::validateFloat($rMax);
@ -97,7 +97,7 @@ class Beta
$rMin = $rMax; $rMin = $rMax;
$rMax = $tmp; $rMax = $tmp;
} }
if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) { if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) {
return Functions::NAN(); return Functions::NAN();
} }

View File

@ -0,0 +1,200 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
class Binomial
{
use BaseValidations;
/**
* BINOMDIST.
*
* Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
* a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
* when trials are independent, and when the probability of success is constant throughout the
* experiment. For example, BINOMDIST can calculate the probability that two of the next three
* babies born are male.
*
* @param mixed (int) $value Number of successes in trials
* @param mixed (int) $trials Number of trials
* @param mixed (float) $probability Probability of success on each trial
* @param mixed (bool) $cumulative
*
* @return float|string
*/
public static function distribution($value, $trials, $probability, $cumulative)
{
$value = Functions::flattenSingleValue($value);
$trials = Functions::flattenSingleValue($trials);
$probability = Functions::flattenSingleValue($probability);
try {
$value = self::validateInt($value);
$trials = self::validateInt($trials);
$probability = self::validateProbability($probability);
$cumulative = self::validateBool($cumulative);
} catch (Exception $e) {
return $e->getMessage();
}
if (($value < 0) || ($value > $trials)) {
return Functions::NAN();
}
if ($cumulative) {
return self::calculateCumulativeBinomial($value, $trials, $probability);
}
return MathTrig::COMBIN($trials, $value) * $probability ** $value * (1 - $probability) ** ($trials - $value);
}
/**
* BINOM.DIST.RANGE.
*
* Returns returns the Binomial Distribution probability for the number of successes from a specified number
* of trials falling into a specified range.
*
* @param mixed (int) $trials Number of trials
* @param mixed (float) $probability Probability of success on each trial
* @param mixed (int) $successes The number of successes in trials
* @param mixed (int) $limit Upper limit for successes in trials
*
* @return float|string
*/
public static function range($trials, $probability, $successes, $limit = null)
{
$trials = Functions::flattenSingleValue($trials);
$probability = Functions::flattenSingleValue($probability);
$successes = Functions::flattenSingleValue($successes);
$limit = ($limit === null) ? $successes : Functions::flattenSingleValue($limit);
try {
$trials = self::validateInt($trials);
$probability = self::validateProbability($probability);
$successes = self::validateInt($successes);
$limit = self::validateInt($limit);
} catch (Exception $e) {
return $e->getMessage();
}
if (($successes < 0) || ($successes > $trials)) {
return Functions::NAN();
}
if (($limit < 0) || ($limit > $trials) || $limit < $successes) {
return Functions::NAN();
}
$summer = 0;
for ($i = $successes; $i <= $limit; ++$i) {
$summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i);
}
return $summer;
}
/**
* NEGBINOMDIST.
*
* Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
* there will be number_f failures before the number_s-th success, when the constant
* probability of a success is probability_s. This function is similar to the binomial
* distribution, except that the number of successes is fixed, and the number of trials is
* variable. Like the binomial, trials are assumed to be independent.
*
* @param mixed (float) $failures Number of Failures
* @param mixed (float) $successes Threshold number of Successes
* @param mixed (float) $probability Probability of success on each trial
*
* @return float|string The result, or a string containing an error
*
* TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST
* The cumulative default should be false to reflect the behaviour of NEGBINOMDIST
*/
public static function negative($failures, $successes, $probability)
{
$failures = Functions::flattenSingleValue($failures);
$successes = Functions::flattenSingleValue($successes);
$probability = Functions::flattenSingleValue($probability);
try {
$failures = self::validateInt($failures);
$successes = self::validateInt($successes);
$probability = self::validateProbability($probability);
} catch (Exception $e) {
return $e->getMessage();
}
if (($failures < 0) || ($successes < 1)) {
return Functions::NAN();
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
if (($failures + $successes - 1) <= 0) {
return Functions::NAN();
}
}
return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) *
($probability ** $successes) * ((1 - $probability) ** $failures);
}
/**
* CRITBINOM.
*
* Returns the smallest value for which the cumulative binomial distribution is greater
* than or equal to a criterion value
*
* @param float $trials number of Bernoulli trials
* @param float $probability probability of a success on each trial
* @param float $alpha criterion value
*
* @return int|string
*/
public static function inverse($trials, $probability, $alpha)
{
$trials = Functions::flattenSingleValue($trials);
$probability = Functions::flattenSingleValue($probability);
$alpha = Functions::flattenSingleValue($alpha);
try {
$trials = self::validateInt($trials);
$probability = self::validateProbability($probability);
$alpha = self::validateFloat($alpha);
} catch (Exception $e) {
return $e->getMessage();
}
if ($trials < 0) {
return Functions::NAN();
} elseif (($alpha < 0.0) || ($alpha > 1.0)) {
return Functions::NAN();
}
$successes = 0;
while (true && $successes <= $trials) {
$result = self::calculateCumulativeBinomial($successes, $trials, $probability);
if ($result >= $alpha) {
break;
}
++$successes;
}
return $successes;
}
/**
* @return float|int
*/
private static function calculateCumulativeBinomial(int $value, int $trials, float $probability)
{
$summer = 0;
for ($i = 0; $i <= $value; ++$i) {
$summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i);
}
return $summer;
}
}

View File

@ -109,13 +109,13 @@ class ChiSquared
$degrees = Functions::flattenSingleValue($degrees); $degrees = Functions::flattenSingleValue($degrees);
try { try {
$probability = self::validateFloat($probability); $probability = self::validateProbability($probability);
$degrees = self::validateInt($degrees); $degrees = self::validateInt($degrees);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }
if ($probability < 0.0 || $probability > 1.0 || $degrees < 1) { if ($degrees < 1) {
return Functions::NAN(); return Functions::NAN();
} }
@ -145,13 +145,13 @@ class ChiSquared
$degrees = Functions::flattenSingleValue($degrees); $degrees = Functions::flattenSingleValue($degrees);
try { try {
$probability = self::validateFloat($probability); $probability = self::validateProbability($probability);
$degrees = self::validateInt($degrees); $degrees = self::validateInt($degrees);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }
if ($probability < 0.0 || $probability > 1.0 || $degrees < 1) { if ($degrees < 1) {
return Functions::NAN(); return Functions::NAN();
} }

View File

@ -87,14 +87,14 @@ class Gamma extends GammaBase
$beta = Functions::flattenSingleValue($beta); $beta = Functions::flattenSingleValue($beta);
try { try {
$probability = self::validateFloat($probability); $probability = self::validateProbability($probability);
$alpha = self::validateFloat($alpha); $alpha = self::validateFloat($alpha);
$beta = self::validateFloat($beta); $beta = self::validateFloat($beta);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }
if (($alpha <= 0.0) || ($beta <= 0.0) || ($probability < 0.0) || ($probability > 1.0)) { if (($alpha <= 0.0) || ($beta <= 0.0)) {
return Functions::NAN(); return Functions::NAN();
} }

View File

@ -15,7 +15,7 @@ class NewtonRaphson
$this->callback = $callback; $this->callback = $callback;
} }
public function execute($probability) public function execute(float $probability)
{ {
$xLo = 100; $xLo = 100;
$xHi = 0; $xHi = 0;

View File

@ -59,13 +59,13 @@ class StudentT
$degrees = Functions::flattenSingleValue($degrees); $degrees = Functions::flattenSingleValue($degrees);
try { try {
$probability = self::validateFloat($probability); $probability = self::validateProbability($probability);
$degrees = self::validateInt($degrees); $degrees = self::validateInt($degrees);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }
if ($probability < 0.0 || $probability > 1.0 || $degrees <= 0) { if ($degrees <= 0) {
return Functions::NAN(); return Functions::NAN();
} }

View File

@ -0,0 +1,31 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PHPUnit\Framework\TestCase;
class BinomDistRangeTest extends TestCase
{
protected function setUp(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
/**
* @dataProvider providerBINOMDISTRANGE
*
* @param mixed $expectedResult
*/
public function testBINOMDISTRANGE($expectedResult, ...$args): void
{
$result = Statistical\Distributions\Binomial::range(...$args);
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
}
public function providerBINOMDISTRANGE()
{
return require 'tests/data/Calculation/Statistical/BINOMDISTRANGE.php';
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PHPUnit\Framework\TestCase;
class BinomInvTest extends TestCase
{
protected function setUp(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
/**
* @dataProvider providerBINOMINV
*
* @param mixed $expectedResult
*/
public function testBINOMINV($expectedResult, ...$args): void
{
$result = Statistical::CRITBINOM(...$args);
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
}
public function providerBINOMINV()
{
return require 'tests/data/Calculation/Statistical/BINOMINV.php';
}
}

View File

@ -49,12 +49,32 @@ return [
'#VALUE!', '#VALUE!',
'NAN', 100, 0.5, true, 'NAN', 100, 0.5, true,
], ],
[
'#VALUE!',
65, 'NAN', 0.5, true,
],
[
'#VALUE!',
65, 100, 'NAN', true,
],
[
'#VALUE!',
65, 100, 0.5, 'NAN',
],
[ [
'#NUM!', '#NUM!',
-5, 100, 0.5, true, -5, 100, 0.5, true,
], ],
[ [
'#NUM!', '#NUM!',
5, 100, 1.5, true, 105, 100, 0.5, true,
],
[
'#NUM!',
65, 100, -0.5, true,
],
[
'#NUM!',
65, 100, 1.5, true,
], ],
]; ];

View File

@ -0,0 +1,72 @@
<?php
return [
[
0.083974967429,
60, 0.75, 48,
],
[
0.5236297934719,
60, 0.75, 45, 50,
],
[
0.0284439668205,
100, 0.5, 0, 40,
],
[
0.728746975926,
100, 0.5, 45, 55,
],
[
0.539794618694,
100, 0.5, 50, 100,
],
[
0.079589237387,
100, 0.5, 50,
],
[
'#VALUE!',
'NaN', 0.5, 50, 100,
],
[
'#VALUE!',
100, 'NaN', 50, 100,
],
[
'#VALUE!',
100, 0.5, 'NaN', 100,
],
[
'#VALUE!',
100, 0.5, 50, 'NaN',
],
[
'#NUM!',
100, -0.5, 50, 100,
],
[
'#NUM!',
100, 1.5, 50, 100,
],
[
'#NUM!',
100, 0.5, -5, 100,
],
[
'#NUM!',
10, 0.5, 50, 100,
],
[
'#NUM!',
100, 0.5, 50, -10,
],
[
'#NUM!',
100, 0.5, 50, -110,
],
[
'#NUM!',
100, 0.5, 50, 40,
],
];

View File

@ -0,0 +1,60 @@
<?php
return [
[
32,
100, 0.3, 0.7,
],
[
4,
6, 0.5, 0.75,
],
[
7,
12, 0.5, 0.75,
],
[
46,
100, 0.5, 0.2,
],
[
50,
100, 0.5, 0.5,
],
[
56,
100, 0.5, 0.9,
],
[
'#VALUE!',
'NaN', 0.5, 0.9,
],
[
'#VALUE!',
100, 'NaN', 0.9,
],
[
'#VALUE!',
100, 0.5, 'NaN',
],
[
'#NUM!',
-1, 0.5, 0.9,
],
[
'#NUM!',
100, -0.5, 0.9,
],
[
'#NUM!',
100, 1.5, 0.9,
],
[
'#NUM!',
100, 0.5, -0.9,
],
[
'#NUM!',
100, 0.5, 1.9,
],
];

View File

@ -1,24 +0,0 @@
<?php
return [
[
4,
6, 0.5, 0.75,
],
[
7,
12, 0.5, 0.75,
],
[
46,
100, 0.5, 0.2,
],
[
50,
100, 0.5, 0.5,
],
[
56,
100, 0.5, 0.9,
],
];

View File

@ -17,6 +17,14 @@ return [
0.057564377784729004, 0.057564377784729004,
15, 12, 0.5, 15, 12, 0.5,
], ],
[
'#VALUE!',
'NaN', 12, 0.5,
],
[
'#VALUE!',
15, 'NaN', 0.5,
],
[ [
'#VALUE!', '#VALUE!',
15, 12, 'NaN', 15, 12, 'NaN',
@ -33,4 +41,12 @@ return [
'#NUM!', '#NUM!',
1, 0.5, 0.25, 1, 0.5, 0.25,
], ],
[
'#NUM!',
6, 12, -0.25,
],
[
'#NUM!',
1, 12, 1.25,
],
]; ];