From be328c33a53be9d5ddf8aafa6678b02dc3687df4 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Sat, 13 Feb 2021 12:55:41 +0100 Subject: [PATCH] Extract all Error functions from the Engineering class into a dedicated Erf and ErfC classes (#1850) * Extract all Error functions from the Engineering class into a dedicated Erf and ErfC classes Retain the original methods in the Engineering class as stubs for BC, but deprecate them. They will be removed for PHPSpreadsheet v2 Note that unit tests still point to the Engineering class stubs; these should be modified to use the Erf and ErfC classes directly when the stubs are removed * Reminder that ERF is used (either directly or Indirectly) in some of the statistical functions as well --- .../Calculation/Calculation.php | 6 +- .../Calculation/Engineering.php | 93 ++----------------- .../Calculation/Engineering/Erf.php | 91 ++++++++++++++++++ .../Calculation/Engineering/ErfC.php | 68 ++++++++++++++ .../Calculation/Statistical.php | 2 +- 5 files changed, 172 insertions(+), 88 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/Engineering/Erf.php create mode 100644 src/PhpSpreadsheet/Calculation/Engineering/ErfC.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 4c7d7b7935..537b937294 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -939,17 +939,17 @@ class Calculation ], 'ERF' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERF'], + 'functionCall' => [Engineering\Erf::class, 'ERF'], 'argumentCount' => '1,2', ], 'ERF.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFPRECISE'], + 'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'], 'argumentCount' => '1', ], 'ERFC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFC'], + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERFC.PRECISE' => [ diff --git a/src/PhpSpreadsheet/Calculation/Engineering.php b/src/PhpSpreadsheet/Calculation/Engineering.php index 1429c7a900..92b68807d9 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering.php +++ b/src/PhpSpreadsheet/Calculation/Engineering.php @@ -1135,34 +1135,6 @@ public static function GESTEP($number, $step = 0) return (int) ($number >= $step); } - // - // Private method to calculate the erf value - // - private static $twoSqrtPi = 1.128379167095512574; - - public static function erfVal($x) - { - if (abs($x) > 2.2) { - return 1 - self::erfcVal($x); - } - $sum = $term = $x; - $xsqr = ($x * $x); - $j = 1; - do { - $term *= $xsqr / $j; - $sum -= $term / (2 * $j + 1); - ++$j; - $term *= $xsqr / $j; - $sum += $term / (2 * $j + 1); - ++$j; - if ($sum == 0.0) { - break; - } - } while (abs($term / $sum) > Functions::PRECISION); - - return self::$twoSqrtPi * $sum; - } - /** * BITAND. * @@ -1276,6 +1248,8 @@ public static function BITRSHIFT($number, $shiftAmount) * Excel Function: * ERF(lower[,upper]) * + * @Deprecated 2.0.0 Use the ERF() method in the Engineering\Erf class instead + * * @param float $lower lower bound for integrating ERF * @param float $upper upper bound for integrating ERF. * If omitted, ERF integrates between zero and lower_limit @@ -1284,19 +1258,7 @@ public static function BITRSHIFT($number, $shiftAmount) */ public static function ERF($lower, $upper = null) { - $lower = Functions::flattenSingleValue($lower); - $upper = Functions::flattenSingleValue($upper); - - if (is_numeric($lower)) { - if ($upper === null) { - return self::erfVal($lower); - } - if (is_numeric($upper)) { - return self::erfVal($upper) - self::erfVal($lower); - } - } - - return Functions::VALUE(); + return Engineering\Erf::ERF($lower, $upper); } /** @@ -1307,48 +1269,15 @@ public static function ERF($lower, $upper = null) * Excel Function: * ERF.PRECISE(limit) * + * @Deprecated 2.0.0 Use the ERFPRECISE() method in the Engineering\Erf class instead + * * @param float $limit bound for integrating ERF * * @return float|string */ public static function ERFPRECISE($limit) { - $limit = Functions::flattenSingleValue($limit); - - return self::ERF($limit); - } - - // - // Private method to calculate the erfc value - // - private static $oneSqrtPi = 0.564189583547756287; - - private static function erfcVal($x) - { - if (abs($x) < 2.2) { - return 1 - self::erfVal($x); - } - if ($x < 0) { - return 2 - self::ERFC(-$x); - } - $a = $n = 1; - $b = $c = $x; - $d = ($x * $x) + 0.5; - $q1 = $q2 = $b / $d; - $t = 0; - do { - $t = $a * $n + $b * $x; - $a = $b; - $b = $t; - $t = $c * $n + $d * $x; - $c = $d; - $d = $t; - $n += 0.5; - $q1 = $q2; - $q2 = $b / $d; - } while ((abs($q1 - $q2) / $q2) > Functions::PRECISION); - - return self::$oneSqrtPi * exp(-$x * $x) * $q2; + return Engineering\Erf::ERFPRECISE($limit); } /** @@ -1364,19 +1293,15 @@ private static function erfcVal($x) * Excel Function: * ERFC(x) * + * @Deprecated 2.0.0 Use the ERFC() method in the Engineering\ErfC class instead + * * @param float $x The lower bound for integrating ERFC * * @return float|string */ public static function ERFC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_numeric($x)) { - return self::erfcVal($x); - } - - return Functions::VALUE(); + return Engineering\ErfC::ERFC($x); } /** diff --git a/src/PhpSpreadsheet/Calculation/Engineering/Erf.php b/src/PhpSpreadsheet/Calculation/Engineering/Erf.php new file mode 100644 index 0000000000..54358ebd78 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/Erf.php @@ -0,0 +1,91 @@ + 2.2) { + return 1 - ErfC::ERFC($value); + } + $sum = $term = $value; + $xsqr = ($value * $value); + $j = 1; + do { + $term *= $xsqr / $j; + $sum -= $term / (2 * $j + 1); + ++$j; + $term *= $xsqr / $j; + $sum += $term / (2 * $j + 1); + ++$j; + if ($sum == 0.0) { + break; + } + } while (abs($term / $sum) > Functions::PRECISION); + + return self::$twoSqrtPi * $sum; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php b/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php new file mode 100644 index 0000000000..31c3bd7582 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php @@ -0,0 +1,68 @@ + Functions::PRECISION); + + return self::$oneSqrtPi * exp(-$value * $value) * $q2; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 6c1d6c7b5e..601dafa015 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -2716,7 +2716,7 @@ public static function NORMDIST($value, $mean, $stdDev, $cumulative) } if ((is_numeric($cumulative)) || (is_bool($cumulative))) { if ($cumulative) { - return 0.5 * (1 + Engineering::erfVal(($value - $mean) / ($stdDev * sqrt(2)))); + 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))));