From 6d5944e2906f81e6ee7edd6a1b00ad1637ff916e Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Mon, 29 Mar 2021 18:29:32 -0700 Subject: [PATCH 1/3] Continue MathTrig Breakup - Penultimate? Continuing the process of breaking MathTrip.php up into smaller classes. This round takes care of about half of what is left, so perhaps one round after this one will finish the job: - ARABIC - COMBIN; also implemented COMBINA - FACTDOUBLE - GCD (which accepts and ignores empty cells as arguments, but returns VALUE if all the arguments are that way; LCM does the same) - LOG_BASE, LOG10, LN - implemented MUNIT - MOD - POWER - RAND, RANDBETWEEN (RANDARRAY is too complicated to implement with this ticket) As you can see from the description, there are some functions which were combined in a single class. When not combined, I adopted PowerKiki's suggestion of using "execute" as the function name. --- .../Calculation/Calculation.php | 26 +- src/PhpSpreadsheet/Calculation/MathTrig.php | 223 +++--------------- .../Calculation/MathTrig/Arabic.php | 95 ++++++++ .../Calculation/MathTrig/Combinations.php | 74 ++++++ .../Calculation/MathTrig/FactDouble.php | 39 +++ .../Calculation/MathTrig/Gcd.php | 69 ++++++ .../Calculation/MathTrig/Helpers.php | 18 +- .../Calculation/MathTrig/Lcm.php | 33 ++- .../Calculation/MathTrig/Logarithms.php | 79 +++++++ .../Calculation/MathTrig/MatrixFunctions.php | 24 ++ .../Calculation/MathTrig/Mod.php | 36 +++ .../Calculation/MathTrig/Power.php | 42 ++++ .../Calculation/MathTrig/Random.php | 39 +++ .../Calculation/functionlist.txt | 2 + .../Functions/MathTrig/ArabicTest.php | 19 +- .../Functions/MathTrig/CombinATest.php | 33 +++ .../Functions/MathTrig/CombinTest.php | 28 ++- .../Functions/MathTrig/FactDoubleTest.php | 19 +- .../Functions/MathTrig/GcdTest.php | 27 ++- .../Calculation/Functions/MathTrig/LnTest.php | 27 +-- .../Functions/MathTrig/Log10Test.php | 27 +-- .../Functions/MathTrig/LogTest.php | 32 ++- .../Functions/MathTrig/MMultTest.php | 11 + .../Functions/MathTrig/MUnitTest.php | 23 ++ .../Functions/MathTrig/ModTest.php | 32 ++- .../Functions/MathTrig/MovedFunctionsTest.php | 13 +- .../Functions/MathTrig/PowerTest.php | 32 ++- .../Functions/MathTrig/RandBetweenTest.php | 46 ++++ .../Functions/MathTrig/RandTest.php | 24 ++ tests/data/Calculation/MathTrig/COMBIN.php | 9 + tests/data/Calculation/MathTrig/COMBINA.php | 135 +++++++++++ tests/data/Calculation/MathTrig/GCD.php | 4 + tests/data/Calculation/MathTrig/LCM.php | 2 + tests/data/Calculation/MathTrig/LN.php | 4 +- tests/data/Calculation/MathTrig/LOG.php | 1 + tests/data/Calculation/MathTrig/LOG10.php | 4 +- tests/data/Calculation/MathTrig/MDETERM.php | 30 +-- tests/data/Calculation/MathTrig/MINVERSE.php | 46 ++-- tests/data/Calculation/MathTrig/MOD.php | 9 +- tests/data/Calculation/MathTrig/POWER.php | 2 + .../data/Calculation/MathTrig/RANDBETWEEN.php | 19 ++ 41 files changed, 1087 insertions(+), 370 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Mod.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Power.php create mode 100644 src/PhpSpreadsheet/Calculation/MathTrig/Random.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php create mode 100644 tests/data/Calculation/MathTrig/COMBINA.php create mode 100644 tests/data/Calculation/MathTrig/RANDBETWEEN.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index c4a88bc3e5..c065e6f4e4 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -288,7 +288,7 @@ class Calculation ], 'ARABIC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ARABIC'], + 'functionCall' => [MathTrig\Arabic::class, 'execute'], 'argumentCount' => '1', ], 'AREAS' => [ @@ -555,12 +555,12 @@ class Calculation ], 'COMBIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COMBIN'], + 'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'], 'argumentCount' => '2', ], 'COMBINA' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [MathTrig\Combinations::class, 'withRepetition'], 'argumentCount' => '2', ], 'COMPLEX' => [ @@ -995,7 +995,7 @@ class Calculation ], 'FACTDOUBLE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FACTDOUBLE'], + 'functionCall' => [MathTrig\FactDouble::class, 'execute'], 'argumentCount' => '1', ], 'FALSE' => [ @@ -1187,7 +1187,7 @@ class Calculation ], 'GCD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'GCD'], + 'functionCall' => [MathTrig\Gcd::class, 'execute'], 'argumentCount' => '1+', ], 'GEOMEAN' => [ @@ -1566,17 +1566,17 @@ class Calculation ], 'LN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'builtinLN'], + 'functionCall' => [MathTrig\Logarithms::class, 'natural'], 'argumentCount' => '1', ], 'LOG' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'logBase'], + 'functionCall' => [MathTrig\Logarithms::class, 'withBase'], 'argumentCount' => '1,2', ], 'LOG10' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'builtinLOG10'], + 'functionCall' => [MathTrig\Logarithms::class, 'base10'], 'argumentCount' => '1', ], 'LOGEST' => [ @@ -1701,7 +1701,7 @@ class Calculation ], 'MOD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MOD'], + 'functionCall' => [MathTrig\Mod::class, 'execute'], 'argumentCount' => '2', ], 'MODE' => [ @@ -1736,7 +1736,7 @@ class Calculation ], 'MUNIT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'funcMUnit'], 'argumentCount' => '1', ], 'N' => [ @@ -1973,7 +1973,7 @@ class Calculation ], 'POWER' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'POWER'], + 'functionCall' => [MathTrig\Power::class, 'execute'], 'argumentCount' => '2', ], 'PPMT' => [ @@ -2043,7 +2043,7 @@ class Calculation ], 'RAND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'RAND'], + 'functionCall' => [MathTrig\Random::class, 'randNoArgs'], 'argumentCount' => '0', ], 'RANDARRAY' => [ @@ -2053,7 +2053,7 @@ class Calculation ], 'RANDBETWEEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'RAND'], + 'functionCall' => [MathTrig\Random::class, 'randBetween'], 'argumentCount' => '2', ], 'RANK' => [ diff --git a/src/PhpSpreadsheet/Calculation/MathTrig.php b/src/PhpSpreadsheet/Calculation/MathTrig.php index 1dbc285489..82eaf62651 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -2,22 +2,15 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use Exception; - class MathTrig { - private static function strSplit(string $roman): array - { - $rslt = str_split($roman); - - return is_array($rslt) ? $rslt : []; - } - /** * ARABIC. * * Converts a Roman numeral to an Arabic numeral. * + * @Deprecated 2.0.0 Use the execute method in the MathTrig\Arabic class instead + * * Excel Function: * ARABIC(text) * @@ -27,69 +20,7 @@ private static function strSplit(string $roman): array */ public static function ARABIC($roman) { - // An empty string should return 0 - $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255); - if ($roman === '') { - return 0; - } - - // Convert the roman numeral to an arabic number - $negativeNumber = $roman[0] === '-'; - if ($negativeNumber) { - $roman = substr($roman, 1); - } - - try { - $arabic = self::calculateArabic(self::strSplit($roman)); - } catch (Exception $e) { - return Functions::VALUE(); // Invalid character detected - } - - if ($negativeNumber) { - $arabic *= -1; // The number should be negative - } - - return $arabic; - } - - /** - * Recursively calculate the arabic value of a roman numeral. - * - * @param int $sum - * @param int $subtract - * - * @return int - */ - protected static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) - { - $lookup = [ - 'M' => 1000, - 'D' => 500, - 'C' => 100, - 'L' => 50, - 'X' => 10, - 'V' => 5, - 'I' => 1, - ]; - - $numeral = array_shift($roman); - if (!isset($lookup[$numeral])) { - throw new Exception('Invalid character detected'); - } - - $arabic = $lookup[$numeral]; - if (count($roman) > 0 && isset($lookup[$roman[0]]) && $arabic < $lookup[$roman[0]]) { - $subtract += $arabic; - } else { - $sum += ($arabic - $subtract); - $subtract = 0; - } - - if (count($roman) > 0) { - self::calculateArabic($roman, $sum, $subtract); - } - - return $sum; + return MathTrig\Arabic::execute($roman); } /** @@ -172,6 +103,8 @@ public static function CEILING($number, $significance = null) * Returns the number of combinations for a given number of items. Use COMBIN to * determine the total possible number of groups for a given number of items. * + * @Deprecated 2.0.0 Use the without method in the MathTrig\Combinations class instead + * * Excel Function: * COMBIN(numObjs,numInSet) * @@ -182,20 +115,7 @@ public static function CEILING($number, $significance = null) */ public static function COMBIN($numObjs, $numInSet) { - $numObjs = Functions::flattenSingleValue($numObjs); - $numInSet = Functions::flattenSingleValue($numInSet); - - if ((is_numeric($numObjs)) && (is_numeric($numInSet))) { - if ($numObjs < $numInSet) { - return Functions::NAN(); - } elseif ($numInSet < 0) { - return Functions::NAN(); - } - - return round(MathTrig\Fact::funcFact($numObjs) / MathTrig\Fact::funcFact($numObjs - $numInSet)) / MathTrig\Fact::funcFact($numInSet); - } - - return Functions::VALUE(); + return MathTrig\Combinations::withoutRepetition($numObjs, $numInSet); } /** @@ -256,6 +176,8 @@ public static function FACT($factVal) * * Returns the double factorial of a number. * + * @Deprecated 2.0.0 Use the execute method in the MathTrig\FactDouble class instead + * * Excel Function: * FACTDOUBLE(factVal) * @@ -265,23 +187,7 @@ public static function FACT($factVal) */ public static function FACTDOUBLE($factVal) { - $factLoop = Functions::flattenSingleValue($factVal); - - if (is_numeric($factLoop)) { - $factLoop = floor($factLoop); - if ($factVal < 0) { - return Functions::NAN(); - } - $factorial = 1; - while ($factLoop > 1) { - $factorial *= $factLoop--; - --$factLoop; - } - - return $factorial; - } - - return Functions::VALUE(); + return MathTrig\FactDouble::execute($factVal); } /** @@ -351,11 +257,6 @@ public static function FLOORPRECISE($number, $significance = 1) return MathTrig\FloorPrecise::funcFloorPrecise($number, $significance); } - private static function evaluateGCD($a, $b) - { - return $b ? self::evaluateGCD($b, $a % $b) : $a; - } - /** * INT. * @@ -384,6 +285,8 @@ public static function INT($number) * The greatest common divisor is the largest integer that divides both * number1 and number2 without a remainder. * + * @Deprecated 2.0.0 Use the execute method in the MathTrig\Gcd class instead + * * Excel Function: * GCD(number1[,number2[, ...]]) * @@ -393,22 +296,7 @@ public static function INT($number) */ public static function GCD(...$args) { - $args = Functions::flattenArray($args); - // Loop through arguments - foreach (Functions::flattenArray($args) as $value) { - if (!is_numeric($value)) { - return Functions::VALUE(); - } elseif ($value < 0) { - return Functions::NAN(); - } - } - - $gcd = (int) array_pop($args); - do { - $gcd = self::evaluateGCD($gcd, (int) array_pop($args)); - } while (!empty($args)); - - return $gcd; + return MathTrig\Gcd::execute(...$args); } /** @@ -438,6 +326,8 @@ public static function LCM(...$args) * * Returns the logarithm of a number to a specified base. The default base is 10. * + * @Deprecated 2.0.0 Use the withBase method in the MathTrig\Logarithms class instead + * * Excel Function: * LOG(number[,base]) * @@ -446,19 +336,9 @@ public static function LCM(...$args) * * @return float|string The result, or a string containing an error */ - public static function logBase($number = null, $base = 10) + public static function logBase($number, $base = 10) { - $number = Functions::flattenSingleValue($number); - $base = ($base === null) ? 10 : (float) Functions::flattenSingleValue($base); - - if ((!is_numeric($base)) || (!is_numeric($number))) { - return Functions::VALUE(); - } - if (($base <= 0) || ($number <= 0)) { - return Functions::NAN(); - } - - return log($number, $base); + return MathTrig\Logarithms::withBase($number, $base); } /** @@ -517,6 +397,8 @@ public static function MMULT($matrixData1, $matrixData2) /** * MOD. * + * @Deprecated 2.0.0 Use the execute method in the MathTrig\Mod class instead + * * @param int $a Dividend * @param int $b Divisor * @@ -524,18 +406,7 @@ public static function MMULT($matrixData1, $matrixData2) */ public static function MOD($a = 1, $b = 1) { - $a = (float) Functions::flattenSingleValue($a); - $b = (float) Functions::flattenSingleValue($b); - - if ($b == 0.0) { - return Functions::DIV0(); - } elseif (($a < 0.0) && ($b > 0.0)) { - return $b - fmod(abs($a), $b); - } elseif (($a > 0.0) && ($b < 0.0)) { - return $b + fmod($a, abs($b)); - } - - return fmod($a, $b); + return MathTrig\Mod::execute($a, $b); } /** @@ -562,6 +433,8 @@ public static function MROUND($number, $multiple) * * Returns the ratio of the factorial of a sum of values to the product of factorials. * + * @Deprecated 2.0.0 Use the funcMultinomial method in the MathTrig\Multinomial class instead + * * @param mixed[] $args An array of mixed values for the Data Series * * @return float|string The result, or a string containing an error @@ -592,27 +465,16 @@ public static function ODD($number) * * Computes x raised to the power y. * + * @Deprecated 2.0.0 Use the execute method in the MathTrig\Power class instead + * * @param float $x * @param float $y * - * @return float|string The result, or a string containing an error + * @return float|int|string The result, or a string containing an error */ public static function POWER($x = 0, $y = 2) { - $x = Functions::flattenSingleValue($x); - $y = Functions::flattenSingleValue($y); - - // Validate parameters - if ($x == 0.0 && $y == 0.0) { - return Functions::NAN(); - } elseif ($x == 0.0 && $y < 0.0) { - return Functions::DIV0(); - } - - // Return - $result = $x ** $y; - - return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN(); + return MathTrig\Power::execute($x, $y); } /** @@ -656,23 +518,18 @@ public static function QUOTIENT($numerator, $denominator) } /** - * RAND. + * RAND/RANDBETWEEN. + * + * @Deprecated 2.0.0 Use the randNoArg or randBetween method in the MathTrig\Random class instead * * @param int $min Minimal value * @param int $max Maximal value * - * @return int Random number + * @return float|int|string Random number */ public static function RAND($min = 0, $max = 0) { - $min = Functions::flattenSingleValue($min); - $max = Functions::flattenSingleValue($max); - - if ($min == 0 && $max == 0) { - return (mt_rand(0, 10000000)) / 10000000; - } - - return mt_rand($min, $max); + return MathTrig\Random::randBetween($min, $max); } /** @@ -1388,19 +1245,15 @@ public static function builtinEXP($number) * * Returns the result of builtin function log after validating args. * + * @Deprecated 2.0.0 Use the natural method in the MathTrig\Logarithms class instead + * * @param mixed $number Should be numeric * * @return float|string Rounded number */ public static function builtinLN($number) { - $number = Functions::flattenSingleValue($number); - - if (!is_numeric($number)) { - return Functions::VALUE(); - } - - return log($number); + return MathTrig\Logarithms::natural($number); } /** @@ -1408,19 +1261,15 @@ public static function builtinLN($number) * * Returns the result of builtin function log after validating args. * + * @Deprecated 2.0.0 Use the base10 method in the MathTrig\Logarithms class instead + * * @param mixed $number Should be numeric * * @return float|string Rounded number */ public static function builtinLOG10($number) { - $number = Functions::flattenSingleValue($number); - - if (!is_numeric($number)) { - return Functions::VALUE(); - } - - return log10($number); + return MathTrig\Logarithms::base10($number); } /** diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php b/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php new file mode 100644 index 0000000000..c31a7eadc2 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php @@ -0,0 +1,95 @@ + 1000, + 'D' => 500, + 'C' => 100, + 'L' => 50, + 'X' => 10, + 'V' => 5, + 'I' => 1, + ]; + + /** + * Recursively calculate the arabic value of a roman numeral. + * + * @param int $sum + * @param int $subtract + * + * @return int + */ + private static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) + { + $numeral = array_shift($roman); + if (!isset(self::ROMAN_LOOKUP[$numeral])) { + throw new Exception('Invalid character detected'); + } + + $arabic = self::ROMAN_LOOKUP[$numeral]; + if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) { + $subtract += $arabic; + } else { + $sum += ($arabic - $subtract); + $subtract = 0; + } + + if (count($roman) > 0) { + self::calculateArabic($roman, $sum, $subtract); + } + + return $sum; + } + + private static function strSplit(string $roman): array + { + $rslt = str_split($roman); + + return is_array($rslt) ? $rslt : []; + } + + /** + * ARABIC. + * + * Converts a Roman numeral to an Arabic numeral. + * + * Excel Function: + * ARABIC(text) + * + * @param string $roman + * + * @return int|string the arabic numberal contrived from the roman numeral + */ + public static function execute($roman) + { + // An empty string should return 0 + $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255); + if ($roman === '') { + return 0; + } + + // Convert the roman numeral to an arabic number + $negativeNumber = $roman[0] === '-'; + if ($negativeNumber) { + $roman = substr($roman, 1); + } + + try { + $arabic = self::calculateArabic(self::strSplit($roman)); + } catch (Exception $e) { + return Functions::VALUE(); // Invalid character detected + } + + if ($negativeNumber) { + $arabic *= -1; // The number should be negative + } + + return $arabic; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php b/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php new file mode 100644 index 0000000000..78b18fc6c2 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php @@ -0,0 +1,74 @@ +getMessage(); + } + + return round(Fact::funcFact($numObjs) / Fact::funcFact($numObjs - $numInSet)) / Fact::funcFact($numInSet); + } + + /** + * COMBIN. + * + * Returns the number of combinations for a given number of items. Use COMBIN to + * determine the total possible number of groups for a given number of items. + * + * Excel Function: + * COMBIN(numObjs,numInSet) + * + * @param mixed $numObjs Number of different objects + * @param mixed $numInSet Number of objects in each combination + * + * @return float|int|string Number of combinations, or a string containing an error + */ + public static function withRepetition($numObjs, $numInSet) + { + try { + $numObjs = Helpers::validateNumericNullSubstitution($numObjs, null); + $numInSet = Helpers::validateNumericNullSubstitution($numInSet, null); + Helpers::validateNotNegative($numInSet); + Helpers::validateNotNegative($numObjs); + $numObjs = (int) $numObjs; + $numInSet = (int) $numInSet; + // Microsoft documentation says following is true, but Excel + // does not enforce this restriction. + //Helpers::validateNotNegative($numObjs - $numInSet); + if ($numObjs === 0) { + Helpers::validateNotNegative(-$numInSet); + + return 1; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return round(Fact::funcFact($numObjs + $numInSet - 1) / Fact::funcFact($numObjs - 1)) / Fact::funcFact($numInSet); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php b/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php new file mode 100644 index 0000000000..ef601d0c44 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php @@ -0,0 +1,39 @@ +getMessage(); + } + + $factLoop = floor($factVal); + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop; + $factLoop -= 2; + } + + return $factorial; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php new file mode 100644 index 0000000000..0c050f597e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php @@ -0,0 +1,69 @@ +getMessage(); + } + + if (count($arrayArgs) <= 0) { + return Functions::VALUE(); + } + $gcd = (int) array_pop($arrayArgs); + do { + $gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs)); + } while (!empty($arrayArgs)); + + return $gcd; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php index 7abcf05094..26716467d5 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php @@ -66,13 +66,27 @@ public static function validateNumericNullSubstitution($number, $substitute) * * @param float|int $number */ - public static function validateNotNegative($number): void + public static function validateNotNegative($number, ?string $except = null): void { if ($number >= 0) { return; } - throw new Exception(Functions::NAN()); + throw new Exception($except ?? Functions::NAN()); + } + + /** + * Confirm number > 0. + * + * @param float|int $number + */ + public static function validatePositive($number, ?string $except = null): void + { + if ($number > 0) { + return; + } + + throw new Exception($except ?? Functions::NAN()); } /** diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php index 3d3f0e469e..14bb52d211 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php @@ -53,12 +53,15 @@ public static function funcLcm(...$args) try { $arrayArgs = []; $anyZeros = 0; + $anyNonNulls = 0; foreach (Functions::flattenArray($args) as $value1) { + $anyNonNulls += (int) ($value1 !== null); $value = Helpers::validateNumericNullSubstitution($value1, 1); Helpers::validateNotNegative($value); $arrayArgs[] = (int) $value; $anyZeros += (int) !((bool) $value); } + self::testNonNulls($anyNonNulls); if ($anyZeros) { return 0; } @@ -76,15 +79,7 @@ public static function funcLcm(...$args) foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower; } - foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { - if (isset($allPoweredFactors[$myPoweredValue])) { - if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { - $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; - } - } else { - $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; - } - } + self::processPoweredFactors($allPoweredFactors, $myPoweredFactors); } foreach ($allPoweredFactors as $allPoweredFactor) { $returnValue *= (int) $allPoweredFactor; @@ -92,4 +87,24 @@ public static function funcLcm(...$args) return $returnValue; } + + private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void + { + foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { + if (isset($allPoweredFactors[$myPoweredValue])) { + if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } else { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } + } + + private static function testNonNulls(bool $anyNonNulls): void + { + if (!$anyNonNulls) { + throw new Exception(Functions::VALUE()); + } + } } diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php b/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php new file mode 100644 index 0000000000..356a0937b1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php @@ -0,0 +1,79 @@ +getMessage(); + } + + return log($number, $base); + } + + /** + * LOG10. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function base10($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log10($number); + } + + /** + * LN. + * + * Returns the result of builtin function log after validating args. + * + * @Deprecated 2.0.0 Use the natural method in the MathTrig\Logarithms class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function natural($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php index 77c8b1e105..ba210fee33 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig; use Exception; +use Matrix\Builder; use Matrix\Exception as MatrixException; use Matrix\Matrix; use PhpOffice\PhpSpreadsheet\Calculation\Functions; @@ -111,4 +112,27 @@ public static function funcMMult($matrixData1, $matrixData2) return $e->getMessage(); } } + + /** + * MUnit. + * + * @param mixed $dimension Number of rows and columns + * + * @return array|string The result, or a string containing an error + */ + public static function funcMUnit($dimension) + { + try { + $dimension = Helpers::validateNumericNullBool($dimension); + Helpers::validatePositive($dimension, Functions::VALUE()); + $matrix = Builder::createFilledMatrix(0, $dimension)->toArray(); + for ($x = 0; $x < $dimension; ++$x) { + $matrix[$x][$x] = 1; + } + + return $matrix; + } catch (Exception $e) { + return $e->getMessage(); + } + } } diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php b/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php new file mode 100644 index 0000000000..affdc327bc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php @@ -0,0 +1,36 @@ +getMessage(); + } + + if (($a < 0.0) && ($b > 0.0)) { + return $b - fmod(abs($a), $b); + } + if (($a > 0.0) && ($b < 0.0)) { + return $b + fmod($a, abs($b)); + } + + return fmod($a, $b); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Power.php b/src/PhpSpreadsheet/Calculation/MathTrig/Power.php new file mode 100644 index 0000000000..efb02f4ab9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Power.php @@ -0,0 +1,42 @@ +getMessage(); + } + + // Validate parameters + if (!$x && !$y) { + return Functions::NAN(); + } + if (!$x && $y < 0.0) { + return Functions::DIV0(); + } + + // Return + $result = $x ** $y; + + return Helpers::numberOrNan($result); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php new file mode 100644 index 0000000000..1bb43c5246 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php @@ -0,0 +1,39 @@ +getMessage(); + } + + return mt_rand($min, $max); + } +} diff --git a/src/PhpSpreadsheet/Calculation/functionlist.txt b/src/PhpSpreadsheet/Calculation/functionlist.txt index 96c28a9739..270715cd09 100644 --- a/src/PhpSpreadsheet/Calculation/functionlist.txt +++ b/src/PhpSpreadsheet/Calculation/functionlist.txt @@ -53,6 +53,7 @@ CODE COLUMN COLUMNS COMBIN +COMBINA COMPLEX CONCAT CONCATENATE @@ -249,6 +250,7 @@ MODE MONTH MROUND MULTINOMIAL +MUNIT N NA NEGBINOMDIST diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php index 7b3a5e15dd..93ed4f2573 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class ArabicTest extends TestCase +class ArabicTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerARABIC * @@ -21,8 +12,12 @@ protected function setUp(): void */ public function testARABIC($expectedResult, $romanNumeral): void { - $result = MathTrig::ARABIC($romanNumeral); - self::assertEquals($expectedResult, $result); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue($romanNumeral); + $sheet->getCell('B1')->setValue('=ARABIC(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertSame($expectedResult, $result); } public function providerARABIC() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php new file mode 100644 index 0000000000..6ab71c7cdc --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php @@ -0,0 +1,33 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($numObjs !== null) { + $sheet->getCell('A1')->setValue($numObjs); + } + if ($numInSet !== null) { + $sheet->getCell('A2')->setValue($numInSet); + } + $sheet->getCell('B1')->setValue('=COMBINA(A1,A2)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerCOMBINA() + { + return require 'tests/data/Calculation/MathTrig/COMBINA.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php index d91563398f..8b2749d8a7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php @@ -2,26 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CombinTest extends TestCase +class CombinTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCOMBIN * * @param mixed $expectedResult + * @param mixed $numObjs + * @param mixed $numInSet */ - public function testCOMBIN($expectedResult, ...$args): void + public function testCOMBIN($expectedResult, $numObjs, $numInSet): void { - $result = MathTrig::COMBIN(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($numObjs !== null) { + $sheet->getCell('A1')->setValue($numObjs); + } + if ($numInSet !== null) { + $sheet->getCell('A2')->setValue($numInSet); + } + $sheet->getCell('B1')->setValue('=COMBIN(A1,A2)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } public function providerCOMBIN() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php index f0b6b146ac..f362720540 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class FactDoubleTest extends TestCase +class FactDoubleTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerFACTDOUBLE * @@ -21,8 +12,12 @@ protected function setUp(): void */ public function testFACTDOUBLE($expectedResult, $value): void { - $result = MathTrig::FACTDOUBLE($value); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue($value); + $sheet->getCell('B1')->setValue('=FACTDOUBLE(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } public function providerFACTDOUBLE() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php index ce1aec3fa4..6d87ccc3ea 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class GcdTest extends TestCase +class GcdTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerGCD * @@ -20,7 +11,21 @@ protected function setUp(): void */ public function testGCD($expectedResult, ...$args): void { - $result = MathTrig::GCD(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $row = 0; + foreach ($args as $arg) { + ++$row; + if ($arg !== null) { + $sheet->getCell("A$row")->setValue($arg); + } + } + if ($row < 1) { + $sheet->getCell('B1')->setValue('=GCD()'); + } else { + $sheet->getCell('B1')->setValue("=GCD(A1:A$row)"); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php index 1910ef0290..e4c46017de 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php @@ -2,30 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PHPUnit\Framework\TestCase; - -class LnTest extends TestCase +class LnTest extends AllSetupTeardown { /** * @dataProvider providerLN * * @param mixed $expectedResult - * @param mixed $val + * @param mixed $number */ - public function testLN($expectedResult, $val = null): void + public function testLN($expectedResult, $number = 'omitted'): void { - if ($val === null) { - $this->expectException(CalcExp::class); - $formula = '=LN()'; + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=LN()'); } else { - $formula = "=LN($val)"; + $sheet->getCell('B1')->setValue('=LN(A1)'); } - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); - $sheet->getCell('A1')->setValue($formula); - $result = $sheet->getCell('A1')->getCalculatedValue(); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-6); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php index e537030cc2..b6afaeda3e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php @@ -2,30 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PHPUnit\Framework\TestCase; - -class Log10Test extends TestCase +class Log10Test extends AllSetupTeardown { /** * @dataProvider providerLN * * @param mixed $expectedResult - * @param mixed $val + * @param mixed $number */ - public function testLN($expectedResult, $val = null): void + public function testLN($expectedResult, $number = 'omitted'): void { - if ($val === null) { - $this->expectException(CalcExp::class); - $formula = '=LOG10()'; + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=LOG10()'); } else { - $formula = "=LOG10($val)"; + $sheet->getCell('B1')->setValue('=LOG10(A1)'); } - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); - $sheet->getCell('A1')->setValue($formula); - $result = $sheet->getCell('A1')->getCalculatedValue(); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-6); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php index 184d83e63b..f27ec94aae 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php @@ -2,25 +2,33 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class LogTest extends TestCase +class LogTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerLOG * * @param mixed $expectedResult + * @param mixed $number + * @param mixed $base */ - public function testLOG($expectedResult, ...$args): void + public function testLOG($expectedResult, $number = 'omitted', $base = 'omitted'): void { - $result = MathTrig::logBase(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($base !== null) { + $sheet->getCell('A2')->setValue($base); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=LOG()'); + } elseif ($base === 'omitted') { + $sheet->getCell('B1')->setValue('=LOG(A1)'); + } else { + $sheet->getCell('B1')->setValue('=LOG(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php index ca8ee5d72e..6c40103c9d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php @@ -28,5 +28,16 @@ public function testOnSpreadsheet(): void $sheet = $this->sheet; $sheet->getCell('A1')->setValue('=MMULT({1,2,3}, {1,2,3})'); // incompatible dimensions self::assertSame('#VALUE!', $sheet->getCell('A1')->getCalculatedValue()); + + $sheet->getCell('A11')->setValue('=MMULT({1, 2, 3, 4}, {5; 6; 7; 8})'); + self::assertEquals(70, $sheet->getCell('A11')->getCalculatedValue()); + $sheet->getCell('A2')->setValue(1); + $sheet->getCell('B2')->setValue(2); + $sheet->getCell('C2')->setValue(3); + $sheet->getCell('D2')->setValue(4); + $sheet->getCell('D3')->setValue(5); + $sheet->getCell('D4')->setValue(6); + $sheet->getCell('A12')->setValue('=MMULT(A2:C2,D2:D4)'); + self::assertEquals(32, $sheet->getCell('A12')->getCalculatedValue()); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php new file mode 100644 index 0000000000..4e9f95cf00 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php @@ -0,0 +1,23 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($dividend !== null) { + $sheet->getCell('A1')->setValue($dividend); + } + if ($divisor !== null) { + $sheet->getCell('A2')->setValue($divisor); + } + if ($dividend === 'omitted') { + $sheet->getCell('B1')->setValue('=MOD()'); + } elseif ($divisor === 'omitted') { + $sheet->getCell('B1')->setValue('=MOD(A1)'); + } else { + $sheet->getCell('B1')->setValue('=MOD(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php index 5b3642ff65..580092cd3b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php @@ -20,6 +20,7 @@ public function testMovedFunctions(): void self::assertEqualsWithDelta(0, MathTrig::builtinACOSH(1), 1E-9); self::assertEqualsWithDelta(3.04192400109863, MathTrig::ACOT(-10), 1E-9); self::assertEqualsWithDelta(-0.20273255405408, MathTrig::ACOTH(-5), 1E-9); + self::assertSame(49, MathTrig::ARABIC('XLIX')); self::assertEqualsWithDelta(0, MathTrig::builtinASIN(0), 1E-9); self::assertEqualsWithDelta(0, MathTrig::builtinASINH(0), 1E-9); self::assertEqualsWithDelta(0, MathTrig::builtinATAN(0), 1E-9); @@ -27,6 +28,7 @@ public function testMovedFunctions(): void self::assertEqualsWithDelta('#DIV/0!', MathTrig::ATAN2(0, 0), 1E-9); self::assertEquals('12', MathTrig::BASE(10, 8)); self::assertEquals(-6, MathTrig::CEILING(-4.5, -2)); + self::assertEquals(15, MathTrig::COMBIN(6, 2)); self::assertEquals(1, MathTrig::builtinCOS(0)); self::assertEquals(1, MathTrig::builtinCOSH(0)); self::assertEquals('#DIV/0!', MathTrig::COT(0)); @@ -35,25 +37,32 @@ public function testMovedFunctions(): void self::assertEquals('#DIV/0!', MathTrig::CSCH(0)); self::assertEquals(6, MathTrig::EVEN(4.5)); self::assertEquals(6, MathTrig::FACT(3)); + self::assertEquals(105, MathTrig::FACTDOUBLE(7)); self::assertEquals(-6, MathTrig::FLOOR(-4.5, 2)); self::assertEquals(0.23, MathTrig::FLOORMATH(0.234, 0.01)); self::assertEquals(-4, MathTrig::FLOORPRECISE(-2.5, 2)); self::assertEquals(-9, MathTrig::INT(-8.3)); self::assertEquals(12, MathTrig::LCM(4, 6)); + self::assertEqualswithDelta(2.302585, MathTrig::builtinLN(10), 1E-6); + self::assertEqualswithDelta(0.306762486567556, MathTrig::logBase(1.5, 3.75), 1E-6); + self::assertEqualswithDelta(0.301030, MathTrig::builtinLOG10(2), 1E-6); self::assertEquals(1, MathTrig::MDETERM([1])); self::assertEquals( [[2, 2], [2, 1]], - MathTrig::MINVERSE([[-0.5, 1.0], [1.0, -1.0]]) + MathTrig::MINVERSE([[-0.5, 1.0], [1.0, -1.0]]) ); self::assertEquals( [[23], [53]], - MathTrig::MMULT([[1, 2], [3, 4]], [[7], [8]]) + MathTrig::MMULT([[1, 2], [3, 4]], [[7], [8]]) ); + self::assertEquals(1, MathTrig::MOD(5, 2)); self::assertEquals(6, MathTrig::MROUND(7.3, 3)); self::assertEquals(1, MathTrig::MULTINOMIAL(1)); self::assertEquals(5, MathTrig::ODD(4.5)); + self::assertEquals(8, MathTrig::POWER(2, 3)); self::assertEquals(8, MathTrig::PRODUCT(1, 2, 4)); self::assertEquals(8, MathTrig::QUOTIENT(17, 2)); + self::assertGreaterThanOrEqual(0, MATHTRIG::RAND()); self::assertEquals('I', MathTrig::ROMAN(1)); self::assertEquals(3.3, MathTrig::builtinROUND(3.27, 1)); self::assertEquals(662, MathTrig::ROUNDDOWN(662.79, 0)); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php index 6749b14ad7..f68941b8ef 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php @@ -2,25 +2,33 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class PowerTest extends TestCase +class PowerTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerPOWER * * @param mixed $expectedResult + * @param mixed $base + * @param mixed $exponent */ - public function testPOWER($expectedResult, ...$args): void + public function testPOWER($expectedResult, $base = 'omitted', $exponent = 'omitted'): void { - $result = MathTrig::POWER(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($base !== null) { + $sheet->getCell('A1')->setValue($base); + } + if ($exponent !== null) { + $sheet->getCell('A2')->setValue($exponent); + } + if ($base === 'omitted') { + $sheet->getCell('B1')->setValue('=POWER()'); + } elseif ($exponent === 'omitted') { + $sheet->getCell('B1')->setValue('=POWER(A1)'); + } else { + $sheet->getCell('B1')->setValue('=POWER(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php new file mode 100644 index 0000000000..0efe8ba696 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php @@ -0,0 +1,46 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $lower = (int) $min; + $upper = (int) $max; + if ($min !== null) { + $sheet->getCell('A1')->setValue($min); + } + if ($max !== null) { + $sheet->getCell('A2')->setValue($max); + } + if ($min === 'omitted') { + $sheet->getCell('B1')->setValue('=RANDBETWEEN()'); + } elseif ($max === 'omitted') { + $sheet->getCell('B1')->setValue('=RANDBETWEEN(A1)'); + } else { + $sheet->getCell('B1')->setValue('=RANDBETWEEN(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + if (is_numeric($expectedResult)) { + self::assertGreaterThanOrEqual($lower, $result); + self::assertLessThanOrEqual($upper, $result); + } else { + self::assertSame($expectedResult, $result); + } + } + + public function providerRANDBETWEEN() + { + return require 'tests/data/Calculation/MathTrig/RANDBETWEEN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php new file mode 100644 index 0000000000..e5e2e1071c --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php @@ -0,0 +1,24 @@ +sheet; + $sheet->getCell('B1')->setValue('=RAND()'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertGreaterThanOrEqual(0, $result); + self::assertLessThanOrEqual(1, $result); + } + + public function testRandException(): void + { + $this->mightHaveException('exception'); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('=RAND(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals(0, $result); + } +} diff --git a/tests/data/Calculation/MathTrig/COMBIN.php b/tests/data/Calculation/MathTrig/COMBIN.php index f5e36a75fe..d163e8836b 100644 --- a/tests/data/Calculation/MathTrig/COMBIN.php +++ b/tests/data/Calculation/MathTrig/COMBIN.php @@ -71,6 +71,11 @@ [ '#VALUE!', 'ABCD', + 2, + ], + [ + '#VALUE!', + 3, 'EFGH', ], [ @@ -123,4 +128,8 @@ 6, 7, ], + [1, 0, 0], + ['#NUM!', 0, 1], + ['#VALUE!', 1, true], + ['#VALUE!', 1, null], ]; diff --git a/tests/data/Calculation/MathTrig/COMBINA.php b/tests/data/Calculation/MathTrig/COMBINA.php new file mode 100644 index 0000000000..c5a26a5f2e --- /dev/null +++ b/tests/data/Calculation/MathTrig/COMBINA.php @@ -0,0 +1,135 @@ + Date: Mon, 29 Mar 2021 18:48:32 -0700 Subject: [PATCH 2/3] Scrutinizer A handful of suggested type changes. --- src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php | 2 +- src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php | 2 +- src/PhpSpreadsheet/Calculation/MathTrig/Random.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php index 14bb52d211..38d9b620a9 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php @@ -101,7 +101,7 @@ private static function processPoweredFactors(array &$allPoweredFactors, array & } } - private static function testNonNulls(bool $anyNonNulls): void + private static function testNonNulls(int $anyNonNulls): void { if (!$anyNonNulls) { throw new Exception(Functions::VALUE()); diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php index ba210fee33..145adfa0ce 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php @@ -123,7 +123,7 @@ public static function funcMMult($matrixData1, $matrixData2) public static function funcMUnit($dimension) { try { - $dimension = Helpers::validateNumericNullBool($dimension); + $dimension = (int) Helpers::validateNumericNullBool($dimension); Helpers::validatePositive($dimension, Functions::VALUE()); $matrix = Builder::createFilledMatrix(0, $dimension)->toArray(); for ($x = 0; $x < $dimension; ++$x) { diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php index 1bb43c5246..1a384fe9a6 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php @@ -27,8 +27,8 @@ public static function randNoArgs() public static function randBetween($min, $max) { try { - $min = Helpers::validateNumericNullBool($min); - $max = Helpers::validateNumericNullBool($max); + $min = (int) Helpers::validateNumericNullBool($min); + $max = (int) Helpers::validateNumericNullBool($max); Helpers::validateNotNegative($max - $min); } catch (Exception $e) { return $e->getMessage(); From e42ef03fa242596411185aaf789623e61888789b Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Wed, 31 Mar 2021 10:31:38 -0700 Subject: [PATCH 3/3] Change 5 Method Names From 'Execute' To 'Evaluate' More descriptive. --- .../Calculation/Calculation.php | 10 +++++----- src/PhpSpreadsheet/Calculation/MathTrig.php | 20 +++++++++---------- .../Calculation/MathTrig/Arabic.php | 2 +- .../Calculation/MathTrig/FactDouble.php | 2 +- .../Calculation/MathTrig/Gcd.php | 2 +- .../Calculation/MathTrig/Mod.php | 2 +- .../Calculation/MathTrig/Power.php | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index c065e6f4e4..ea57e0eb94 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -288,7 +288,7 @@ class Calculation ], 'ARABIC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig\Arabic::class, 'execute'], + 'functionCall' => [MathTrig\Arabic::class, 'evaluate'], 'argumentCount' => '1', ], 'AREAS' => [ @@ -995,7 +995,7 @@ class Calculation ], 'FACTDOUBLE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig\FactDouble::class, 'execute'], + 'functionCall' => [MathTrig\FactDouble::class, 'evaluate'], 'argumentCount' => '1', ], 'FALSE' => [ @@ -1187,7 +1187,7 @@ class Calculation ], 'GCD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig\Gcd::class, 'execute'], + 'functionCall' => [MathTrig\Gcd::class, 'evaluate'], 'argumentCount' => '1+', ], 'GEOMEAN' => [ @@ -1701,7 +1701,7 @@ class Calculation ], 'MOD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig\Mod::class, 'execute'], + 'functionCall' => [MathTrig\Mod::class, 'evaluate'], 'argumentCount' => '2', ], 'MODE' => [ @@ -1973,7 +1973,7 @@ class Calculation ], 'POWER' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig\Power::class, 'execute'], + 'functionCall' => [MathTrig\Power::class, 'evaluate'], 'argumentCount' => '2', ], 'PPMT' => [ diff --git a/src/PhpSpreadsheet/Calculation/MathTrig.php b/src/PhpSpreadsheet/Calculation/MathTrig.php index 82eaf62651..e960d7ef0e 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -9,7 +9,7 @@ class MathTrig * * Converts a Roman numeral to an Arabic numeral. * - * @Deprecated 2.0.0 Use the execute method in the MathTrig\Arabic class instead + * @Deprecated 2.0.0 Use the evaluate method in the MathTrig\Arabic class instead * * Excel Function: * ARABIC(text) @@ -20,7 +20,7 @@ class MathTrig */ public static function ARABIC($roman) { - return MathTrig\Arabic::execute($roman); + return MathTrig\Arabic::evaluate($roman); } /** @@ -176,7 +176,7 @@ public static function FACT($factVal) * * Returns the double factorial of a number. * - * @Deprecated 2.0.0 Use the execute method in the MathTrig\FactDouble class instead + * @Deprecated 2.0.0 Use the evaluate method in the MathTrig\FactDouble class instead * * Excel Function: * FACTDOUBLE(factVal) @@ -187,7 +187,7 @@ public static function FACT($factVal) */ public static function FACTDOUBLE($factVal) { - return MathTrig\FactDouble::execute($factVal); + return MathTrig\FactDouble::evaluate($factVal); } /** @@ -285,7 +285,7 @@ public static function INT($number) * The greatest common divisor is the largest integer that divides both * number1 and number2 without a remainder. * - * @Deprecated 2.0.0 Use the execute method in the MathTrig\Gcd class instead + * @Deprecated 2.0.0 Use the evaluate method in the MathTrig\Gcd class instead * * Excel Function: * GCD(number1[,number2[, ...]]) @@ -296,7 +296,7 @@ public static function INT($number) */ public static function GCD(...$args) { - return MathTrig\Gcd::execute(...$args); + return MathTrig\Gcd::evaluate(...$args); } /** @@ -397,7 +397,7 @@ public static function MMULT($matrixData1, $matrixData2) /** * MOD. * - * @Deprecated 2.0.0 Use the execute method in the MathTrig\Mod class instead + * @Deprecated 2.0.0 Use the evaluate method in the MathTrig\Mod class instead * * @param int $a Dividend * @param int $b Divisor @@ -406,7 +406,7 @@ public static function MMULT($matrixData1, $matrixData2) */ public static function MOD($a = 1, $b = 1) { - return MathTrig\Mod::execute($a, $b); + return MathTrig\Mod::evaluate($a, $b); } /** @@ -465,7 +465,7 @@ public static function ODD($number) * * Computes x raised to the power y. * - * @Deprecated 2.0.0 Use the execute method in the MathTrig\Power class instead + * @Deprecated 2.0.0 Use the evaluate method in the MathTrig\Power class instead * * @param float $x * @param float $y @@ -474,7 +474,7 @@ public static function ODD($number) */ public static function POWER($x = 0, $y = 2) { - return MathTrig\Power::execute($x, $y); + return MathTrig\Power::evaluate($x, $y); } /** diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php b/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php index c31a7eadc2..320856b934 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php @@ -66,7 +66,7 @@ private static function strSplit(string $roman): array * * @return int|string the arabic numberal contrived from the roman numeral */ - public static function execute($roman) + public static function evaluate($roman) { // An empty string should return 0 $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255); diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php b/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php index ef601d0c44..4b76014435 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php @@ -18,7 +18,7 @@ class FactDouble * * @return float|int|string Double Factorial, or a string containing an error */ - public static function execute($factVal) + public static function evaluate($factVal) { try { $factVal = Helpers::validateNumericNullSubstitution($factVal, 0); diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php index 0c050f597e..21c226990c 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php @@ -41,7 +41,7 @@ private static function evaluateGCD($a, $b) * * @return float|int|string Greatest Common Divisor, or a string containing an error */ - public static function execute(...$args) + public static function evaluate(...$args) { try { $arrayArgs = []; diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php b/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php index affdc327bc..b2e3cf4bf0 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php @@ -14,7 +14,7 @@ class Mod * * @return float|int|string Remainder, or a string containing an error */ - public static function execute($a, $b) + public static function evaluate($a, $b) { try { $a = Helpers::validateNumericNullBool($a); diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Power.php b/src/PhpSpreadsheet/Calculation/MathTrig/Power.php index efb02f4ab9..70d177cd3d 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Power.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Power.php @@ -17,7 +17,7 @@ class Power * * @return float|int|string The result, or a string containing an error */ - public static function execute($x, $y) + public static function evaluate($x, $y) { try { $x = Helpers::validateNumericNullBool($x);