diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 0538e28fad..967d05e5fe 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -1586,22 +1586,22 @@ class Calculation ], 'LOGINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGINV'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOGNORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGNORMDIST'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'], 'argumentCount' => '3', ], 'LOGNORM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGNORMDIST2'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'], 'argumentCount' => '4', ], 'LOGNORM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGINV'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOOKUP' => [ @@ -1776,42 +1776,42 @@ class Calculation ], 'NORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMDIST'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMDIST'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORMINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMINV'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMINV'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORMSDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSDIST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'], 'argumentCount' => '1', ], 'NORM.S.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSDIST2'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'], 'argumentCount' => '1,2', ], 'NORMSINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSINV'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NORM.S.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSINV'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NOT' => [ @@ -2651,12 +2651,12 @@ class Calculation ], 'ZTEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'ZTEST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], 'Z.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'ZTEST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], ]; diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 1576af983d..3e90b21d71 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -21,90 +21,6 @@ class Statistical const MAX_ITERATIONS = 256; const SQRT2PI = 2.5066282746310005024157652848110452530069867406099; - /* - * inverse_ncdf.php - * ------------------- - * begin : Friday, January 16, 2004 - * copyright : (C) 2004 Michael Nickerson - * email : nickersonm@yahoo.com - * - */ - private static function inverseNcdf($p) - { - // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to - // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as - // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html - // I have not checked the accuracy of this implementation. Be aware that PHP - // will truncate the coeficcients to 14 digits. - - // You have permission to use and distribute this function freely for - // whatever purpose you want, but please show common courtesy and give credit - // where credit is due. - - // Input paramater is $p - probability - where 0 < p < 1. - - // Coefficients in rational approximations - static $a = [ - 1 => -3.969683028665376e+01, - 2 => 2.209460984245205e+02, - 3 => -2.759285104469687e+02, - 4 => 1.383577518672690e+02, - 5 => -3.066479806614716e+01, - 6 => 2.506628277459239e+00, - ]; - - static $b = [ - 1 => -5.447609879822406e+01, - 2 => 1.615858368580409e+02, - 3 => -1.556989798598866e+02, - 4 => 6.680131188771972e+01, - 5 => -1.328068155288572e+01, - ]; - - static $c = [ - 1 => -7.784894002430293e-03, - 2 => -3.223964580411365e-01, - 3 => -2.400758277161838e+00, - 4 => -2.549732539343734e+00, - 5 => 4.374664141464968e+00, - 6 => 2.938163982698783e+00, - ]; - - static $d = [ - 1 => 7.784695709041462e-03, - 2 => 3.224671290700398e-01, - 3 => 2.445134137142996e+00, - 4 => 3.754408661907416e+00, - ]; - - // Define lower and upper region break-points. - $p_low = 0.02425; //Use lower region approx. below this - $p_high = 1 - $p_low; //Use upper region approx. above this - - if (0 < $p && $p < $p_low) { - // Rational approximation for lower region. - $q = sqrt(-2 * log($p)); - - return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / - (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); - } elseif ($p_low <= $p && $p <= $p_high) { - // Rational approximation for central region. - $q = $p - 0.5; - $r = $q * $q; - - return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / - ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); - } elseif ($p_high < $p && $p < 1) { - // Rational approximation for upper region. - $q = sqrt(-2 * log(1 - $p)); - - return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / - (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); - } - // If 0 < p < 1, return a null value - return Functions::NULL(); - } - /** * AVEDEV. * @@ -766,7 +682,7 @@ public static function GAUSS($value) return Functions::VALUE(); } - return self::NORMDIST($value, 0, 1, true) - 0.5; + return Statistical\Distributions\Normal::distribution($value, 0, 1, true) - 0.5; } /** @@ -1048,6 +964,11 @@ public static function LOGEST($yValues, $xValues = null, $const = true, $stats = * * Returns the inverse of the normal cumulative distribution * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\LogNormal::inverse() + * Use the inverse() method in the Statistical\Distributions\LogNormal class instead + * * @param float $probability * @param float $mean * @param float $stdDev @@ -1060,19 +981,7 @@ public static function LOGEST($yValues, $xValues = null, $const = true, $stats = */ public static function LOGINV($probability, $mean, $stdDev) { - $probability = Functions::flattenSingleValue($probability); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - return exp($mean + $stdDev * self::NORMSINV($probability)); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::inverse($probability, $mean, $stdDev); } /** @@ -1081,6 +990,11 @@ public static function LOGINV($probability, $mean, $stdDev) * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed * with parameters mean and standard_dev. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\LogNormal::cumulative() + * Use the cumulative() method in the Statistical\Distributions\LogNormal class instead + * * @param float $value * @param float $mean * @param float $stdDev @@ -1089,19 +1003,7 @@ public static function LOGINV($probability, $mean, $stdDev) */ public static function LOGNORMDIST($value, $mean, $stdDev) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($value <= 0) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - return self::NORMSDIST((log($value) - $mean) / $stdDev); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::cumulative($value, $mean, $stdDev); } /** @@ -1110,6 +1012,11 @@ public static function LOGNORMDIST($value, $mean, $stdDev) * Returns the lognormal distribution of x, where ln(x) is normally distributed * with parameters mean and standard_dev. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\LogNormal::distribution() + * Use the distribution() method in the Statistical\Distributions\LogNormal class instead + * * @param float $value * @param float $mean * @param float $stdDev @@ -1119,25 +1026,7 @@ public static function LOGNORMDIST($value, $mean, $stdDev) */ public static function LOGNORMDIST2($value, $mean, $stdDev, $cumulative = false) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - $cumulative = (bool) Functions::flattenSingleValue($cumulative); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($value <= 0) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - if ($cumulative === true) { - return self::NORMSDIST2((log($value) - $mean) / $stdDev, true); - } - - return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * - exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::distribution($value, $mean, $stdDev, $cumulative); } /** @@ -1329,7 +1218,7 @@ public static function MODE(...$args) * * @Deprecated 1.18.0 * - * @see Statistical\Distributions\Binomial::negative::mode() + * @see Statistical\Distributions\Binomial::negative() * Use the negative() method in the Statistical\Distributions\Binomial class instead * * @param mixed (float) $failures Number of Failures @@ -1350,6 +1239,11 @@ public static function NEGBINOMDIST($failures, $successes, $probability) * function has a very wide range of applications in statistics, including hypothesis * testing. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Normal::distribution() + * Use the distribution() method in the Statistical\Distributions\Normal class instead + * * @param mixed (float) $value * @param mixed (float) $mean Mean Value * @param mixed (float) $stdDev Standard Deviation @@ -1359,24 +1253,7 @@ public static function NEGBINOMDIST($failures, $successes, $probability) */ public static function NORMDIST($value, $mean, $stdDev, $cumulative) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if ($stdDev < 0) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2)))); - } - - return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Normal::distribution($value, $mean, $stdDev, $cumulative); } /** @@ -1384,6 +1261,11 @@ public static function NORMDIST($value, $mean, $stdDev, $cumulative) * * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Normal::inverse() + * Use the inverse() method in the Statistical\Distributions\Normal class instead + * * @param mixed (float) $probability * @param mixed (float) $mean Mean Value * @param mixed (float) $stdDev Standard Deviation @@ -1392,22 +1274,7 @@ public static function NORMDIST($value, $mean, $stdDev, $cumulative) */ public static function NORMINV($probability, $mean, $stdDev) { - $probability = Functions::flattenSingleValue($probability); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ($stdDev < 0) { - return Functions::NAN(); - } - - return (self::inverseNcdf($probability) * $stdDev) + $mean; - } - - return Functions::VALUE(); + return Statistical\Distributions\Normal::inverse($probability, $mean, $stdDev); } /** @@ -1417,18 +1284,18 @@ public static function NORMINV($probability, $mean, $stdDev) * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a * table of standard normal curve areas. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::cumulative() + * Use the cumulative() method in the Statistical\Distributions\StandardNormal class instead + * * @param mixed (float) $value * * @return float|string The result, or a string containing an error */ public static function NORMSDIST($value) { - $value = Functions::flattenSingleValue($value); - if (!is_numeric($value)) { - return Functions::VALUE(); - } - - return self::NORMDIST($value, 0, 1, true); + return Statistical\Distributions\StandardNormal::cumulative($value); } /** @@ -1438,6 +1305,11 @@ public static function NORMSDIST($value) * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a * table of standard normal curve areas. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::distribution() + * Use the distribution() method in the Statistical\Distributions\StandardNormal class instead + * * @param mixed (float) $value * @param mixed (bool) $cumulative * @@ -1445,13 +1317,7 @@ public static function NORMSDIST($value) */ public static function NORMSDIST2($value, $cumulative) { - $value = Functions::flattenSingleValue($value); - if (!is_numeric($value)) { - return Functions::VALUE(); - } - $cumulative = (bool) Functions::flattenSingleValue($cumulative); - - return self::NORMDIST($value, 0, 1, $cumulative); + return Statistical\Distributions\StandardNormal::distribution($value, $cumulative); } /** @@ -1459,13 +1325,18 @@ public static function NORMSDIST2($value, $cumulative) * * Returns the inverse of the standard normal cumulative distribution * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::inverse() + * Use the inverse() method in the Statistical\Distributions\StandardNormal class instead + * * @param mixed (float) $value * * @return float|string The result, or a string containing an error */ public static function NORMSINV($value) { - return self::NORMINV($value, 0, 1); + return Statistical\Distributions\StandardNormal::inverse($value); } /** @@ -2098,6 +1969,11 @@ public static function WEIBULL($value, $alpha, $beta, $cumulative) * For a given hypothesized population mean, x, Z.TEST returns the probability that the sample mean would be * greater than the average of observations in the data set (array) — that is, the observed sample mean. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::zTest() + * Use the zTest() method in the Statistical\Distributions\StandardNormal class instead + * * @param float $dataSet * @param float $m0 Alpha Parameter * @param float $sigma Beta Parameter @@ -2106,15 +1982,6 @@ public static function WEIBULL($value, $alpha, $beta, $cumulative) */ public static function ZTEST($dataSet, $m0, $sigma = null) { - $dataSet = Functions::flattenArrayIndexed($dataSet); - $m0 = Functions::flattenSingleValue($m0); - $sigma = Functions::flattenSingleValue($sigma); - - if ($sigma === null) { - $sigma = StandardDeviations::STDEV($dataSet); - } - $n = count($dataSet); - - return 1 - self::NORMSDIST((Averages::average($dataSet) - $m0) / ($sigma / sqrt($n))); + return Statistical\Distributions\StandardNormal::zTest($dataSet, $m0, $sigma); } } diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php b/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php index c9ef0f166b..40adc9e31a 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php @@ -4,7 +4,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception; use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\Statistical; class Confidence { @@ -39,6 +38,6 @@ public static function CONFIDENCE($alpha, $stdDev, $size) return Functions::NAN(); } - return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); + return Distributions\StandardNormal::inverse(1 - $alpha / 2) * $stdDev / sqrt($size); } } diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php new file mode 100644 index 0000000000..87b464fa4a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php @@ -0,0 +1,121 @@ +getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return Functions::NAN(); + } + + return StandardNormal::cumulative((log($value) - $mean) / $stdDev); + } + + /** + * LOGNORM.DIST. + * + * Returns the lognormal distribution of x, where ln(x) is normally distributed + * with parameters mean and standard_dev. + * + * @param mixed (float) $value + * @param mixed (float) $mean + * @param mixed (float) $stdDev + * @param mixed (bool) $cumulative + * + * @return float|string The result, or a string containing an error + */ + public static function distribution($value, $mean, $stdDev, $cumulative = false) + { + $value = Functions::flattenSingleValue($value); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + $cumulative = Functions::flattenSingleValue($cumulative); + + try { + $value = self::validateFloat($value); + $mean = self::validateFloat($mean); + $stdDev = self::validateFloat($stdDev); + $cumulative = self::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return Functions::NAN(); + } + + if ($cumulative === true) { + return StandardNormal::distribution((log($value) - $mean) / $stdDev, true); + } + + return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * + exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); + } + + /** + * LOGINV. + * + * Returns the inverse of the normal cumulative distribution + * + * @param mixed (float) $probability + * @param mixed (float) $mean + * @param mixed (float) $stdDev + * + * @return float|string The result, or a string containing an error + * + * @TODO Try implementing P J Acklam's refinement algorithm for greater + * accuracy if I can get my head round the mathematics + * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ + */ + public static function inverse($probability, $mean, $stdDev) + { + $probability = Functions::flattenSingleValue($probability); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + + try { + $probability = self::validateProbability($probability); + $mean = self::validateFloat($mean); + $stdDev = self::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev <= 0) { + return Functions::NAN(); + } + + return exp($mean + $stdDev * StandardNormal::inverse($probability)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php new file mode 100644 index 0000000000..b0c5552a04 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php @@ -0,0 +1,168 @@ +getMessage(); + } + + if ($stdDev < 0) { + return Functions::NAN(); + } + + if ($cumulative) { + return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2)))); + } + + return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); + } + + /** + * NORMINV. + * + * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. + * + * @param mixed (float) $probability + * @param mixed (float) $mean Mean Value + * @param mixed (float) $stdDev Standard Deviation + * + * @return float|string The result, or a string containing an error + */ + public static function inverse($probability, $mean, $stdDev) + { + $probability = Functions::flattenSingleValue($probability); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + + try { + $probability = self::validateProbability($probability); + $mean = self::validateFloat($mean); + $stdDev = self::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev < 0) { + return Functions::NAN(); + } + + return (self::inverseNcdf($probability) * $stdDev) + $mean; + } + + /* + * inverse_ncdf.php + * ------------------- + * begin : Friday, January 16, 2004 + * copyright : (C) 2004 Michael Nickerson + * email : nickersonm@yahoo.com + * + */ + private static function inverseNcdf($p) + { + // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to + // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as + // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html + // I have not checked the accuracy of this implementation. Be aware that PHP + // will truncate the coeficcients to 14 digits. + + // You have permission to use and distribute this function freely for + // whatever purpose you want, but please show common courtesy and give credit + // where credit is due. + + // Input paramater is $p - probability - where 0 < p < 1. + + // Coefficients in rational approximations + static $a = [ + 1 => -3.969683028665376e+01, + 2 => 2.209460984245205e+02, + 3 => -2.759285104469687e+02, + 4 => 1.383577518672690e+02, + 5 => -3.066479806614716e+01, + 6 => 2.506628277459239e+00, + ]; + + static $b = [ + 1 => -5.447609879822406e+01, + 2 => 1.615858368580409e+02, + 3 => -1.556989798598866e+02, + 4 => 6.680131188771972e+01, + 5 => -1.328068155288572e+01, + ]; + + static $c = [ + 1 => -7.784894002430293e-03, + 2 => -3.223964580411365e-01, + 3 => -2.400758277161838e+00, + 4 => -2.549732539343734e+00, + 5 => 4.374664141464968e+00, + 6 => 2.938163982698783e+00, + ]; + + static $d = [ + 1 => 7.784695709041462e-03, + 2 => 3.224671290700398e-01, + 3 => 2.445134137142996e+00, + 4 => 3.754408661907416e+00, + ]; + + // Define lower and upper region break-points. + $p_low = 0.02425; //Use lower region approx. below this + $p_high = 1 - $p_low; //Use upper region approx. above this + + if (0 < $p && $p < $p_low) { + // Rational approximation for lower region. + $q = sqrt(-2 * log($p)); + + return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / + (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); + } elseif ($p_high < $p && $p < 1) { + // Rational approximation for upper region. + $q = sqrt(-2 * log(1 - $p)); + + return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / + (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); + } + + // Rational approximation for central region. + $q = $p - 0.5; + $r = $q * $q; + + return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / + ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php new file mode 100644 index 0000000000..c3049f6d65 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php @@ -0,0 +1,89 @@ +