Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract all Error functions from the Engineering class into a dedicated Erf and ErfC classes #1850

Merged
merged 2 commits into from
Feb 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => [
Expand Down
93 changes: 9 additions & 84 deletions src/PhpSpreadsheet/Calculation/Engineering.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand Down
91 changes: 91 additions & 0 deletions src/PhpSpreadsheet/Calculation/Engineering/Erf.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
68 changes: 68 additions & 0 deletions src/PhpSpreadsheet/Calculation/Engineering/ErfC.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Calculation/Statistical.php
Original file line number Diff line number Diff line change
Expand Up @@ -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))));
Expand Down