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
This commit is contained in:
parent
61d2e6dcd3
commit
be328c33a5
|
|
@ -939,17 +939,17 @@ class Calculation
|
||||||
],
|
],
|
||||||
'ERF' => [
|
'ERF' => [
|
||||||
'category' => Category::CATEGORY_ENGINEERING,
|
'category' => Category::CATEGORY_ENGINEERING,
|
||||||
'functionCall' => [Engineering::class, 'ERF'],
|
'functionCall' => [Engineering\Erf::class, 'ERF'],
|
||||||
'argumentCount' => '1,2',
|
'argumentCount' => '1,2',
|
||||||
],
|
],
|
||||||
'ERF.PRECISE' => [
|
'ERF.PRECISE' => [
|
||||||
'category' => Category::CATEGORY_ENGINEERING,
|
'category' => Category::CATEGORY_ENGINEERING,
|
||||||
'functionCall' => [Engineering::class, 'ERFPRECISE'],
|
'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'],
|
||||||
'argumentCount' => '1',
|
'argumentCount' => '1',
|
||||||
],
|
],
|
||||||
'ERFC' => [
|
'ERFC' => [
|
||||||
'category' => Category::CATEGORY_ENGINEERING,
|
'category' => Category::CATEGORY_ENGINEERING,
|
||||||
'functionCall' => [Engineering::class, 'ERFC'],
|
'functionCall' => [Engineering\ErfC::class, 'ERFC'],
|
||||||
'argumentCount' => '1',
|
'argumentCount' => '1',
|
||||||
],
|
],
|
||||||
'ERFC.PRECISE' => [
|
'ERFC.PRECISE' => [
|
||||||
|
|
|
||||||
|
|
@ -1135,34 +1135,6 @@ class Engineering
|
||||||
return (int) ($number >= $step);
|
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.
|
* BITAND.
|
||||||
*
|
*
|
||||||
|
|
@ -1276,6 +1248,8 @@ class Engineering
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* ERF(lower[,upper])
|
* 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 $lower lower bound for integrating ERF
|
||||||
* @param float $upper upper bound for integrating ERF.
|
* @param float $upper upper bound for integrating ERF.
|
||||||
* If omitted, ERF integrates between zero and lower_limit
|
* If omitted, ERF integrates between zero and lower_limit
|
||||||
|
|
@ -1284,19 +1258,7 @@ class Engineering
|
||||||
*/
|
*/
|
||||||
public static function ERF($lower, $upper = null)
|
public static function ERF($lower, $upper = null)
|
||||||
{
|
{
|
||||||
$lower = Functions::flattenSingleValue($lower);
|
return Engineering\Erf::ERF($lower, $upper);
|
||||||
$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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1307,48 +1269,15 @@ class Engineering
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* ERF.PRECISE(limit)
|
* ERF.PRECISE(limit)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 2.0.0 Use the ERFPRECISE() method in the Engineering\Erf class instead
|
||||||
|
*
|
||||||
* @param float $limit bound for integrating ERF
|
* @param float $limit bound for integrating ERF
|
||||||
*
|
*
|
||||||
* @return float|string
|
* @return float|string
|
||||||
*/
|
*/
|
||||||
public static function ERFPRECISE($limit)
|
public static function ERFPRECISE($limit)
|
||||||
{
|
{
|
||||||
$limit = Functions::flattenSingleValue($limit);
|
return Engineering\Erf::ERFPRECISE($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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1364,19 +1293,15 @@ class Engineering
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* ERFC(x)
|
* 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
|
* @param float $x The lower bound for integrating ERFC
|
||||||
*
|
*
|
||||||
* @return float|string
|
* @return float|string
|
||||||
*/
|
*/
|
||||||
public static function ERFC($x)
|
public static function ERFC($x)
|
||||||
{
|
{
|
||||||
$x = Functions::flattenSingleValue($x);
|
return Engineering\ErfC::ERFC($x);
|
||||||
|
|
||||||
if (is_numeric($x)) {
|
|
||||||
return self::erfcVal($x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
|
class Erf
|
||||||
|
{
|
||||||
|
private static $twoSqrtPi = 1.128379167095512574;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERF.
|
||||||
|
*
|
||||||
|
* Returns the error function integrated between the lower and upper bound arguments.
|
||||||
|
*
|
||||||
|
* Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments,
|
||||||
|
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
|
||||||
|
* improved, so that it can now calculate the function for both positive and negative ranges.
|
||||||
|
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* ERF(lower[,upper])
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function ERF($lower, $upper = null)
|
||||||
|
{
|
||||||
|
$lower = Functions::flattenSingleValue($lower);
|
||||||
|
$upper = Functions::flattenSingleValue($upper);
|
||||||
|
|
||||||
|
if (is_numeric($lower)) {
|
||||||
|
if ($upper === null) {
|
||||||
|
return self::erfValue($lower);
|
||||||
|
}
|
||||||
|
if (is_numeric($upper)) {
|
||||||
|
return self::erfValue($upper) - self::erfValue($lower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERFPRECISE.
|
||||||
|
*
|
||||||
|
* Returns the error function integrated between the lower and upper bound arguments.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* ERF.PRECISE(limit)
|
||||||
|
*
|
||||||
|
* @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 erf value
|
||||||
|
//
|
||||||
|
public static function erfValue($value)
|
||||||
|
{
|
||||||
|
if (abs($value) > 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
|
class ErfC
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* ERFC.
|
||||||
|
*
|
||||||
|
* Returns the complementary ERF function integrated between x and infinity
|
||||||
|
*
|
||||||
|
* Note: In Excel 2007 or earlier, if you input a negative value for the lower bound argument,
|
||||||
|
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
|
||||||
|
* improved, so that it can now calculate the function for both positive and negative x values.
|
||||||
|
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* ERFC(x)
|
||||||
|
*
|
||||||
|
* @param float $value The lower bound for integrating ERFC
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function ERFC($value)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return self::erfcValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private method to calculate the erfc value
|
||||||
|
//
|
||||||
|
private static $oneSqrtPi = 0.564189583547756287;
|
||||||
|
|
||||||
|
private static function erfcValue($value)
|
||||||
|
{
|
||||||
|
if (abs($value) < 2.2) {
|
||||||
|
return 1 - Erf::erfValue($value);
|
||||||
|
}
|
||||||
|
if ($value < 0) {
|
||||||
|
return 2 - self::ERFC(-$value);
|
||||||
|
}
|
||||||
|
$a = $n = 1;
|
||||||
|
$b = $c = $value;
|
||||||
|
$d = ($value * $value) + 0.5;
|
||||||
|
$q1 = $q2 = $b / $d;
|
||||||
|
do {
|
||||||
|
$t = $a * $n + $b * $value;
|
||||||
|
$a = $b;
|
||||||
|
$b = $t;
|
||||||
|
$t = $c * $n + $d * $value;
|
||||||
|
$c = $d;
|
||||||
|
$d = $t;
|
||||||
|
$n += 0.5;
|
||||||
|
$q1 = $q2;
|
||||||
|
$q2 = $b / $d;
|
||||||
|
} while ((abs($q1 - $q2) / $q2) > Functions::PRECISION);
|
||||||
|
|
||||||
|
return self::$oneSqrtPi * exp(-$value * $value) * $q2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2716,7 +2716,7 @@ class Statistical
|
||||||
}
|
}
|
||||||
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
|
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
|
||||||
if ($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))));
|
return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev))));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue