From 1b96c95a4414940b4bb08637e8ec0edd94f5cb0f Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Wed, 25 Jul 2018 14:38:44 +0100 Subject: [PATCH] Add new Complex Number Functions introduced in MS Excel 2013 (#601) * - Refactored Complex Engineering Functions to use external complex number library - Added calculation engine support for the new complex number functions that were added in MS Excel 2013 - IMCOSH() Returns the hyperbolic cosine of a complex number - IMCOT() Returns the cotangent of a complex number - IMCSC() Returns the cosecant of a complex number - IMCSCH() Returns the hyperbolic cosecant of a complex number - IMSEC() Returns the secant of a complex number - IMSECH() Returns the hyperbolic secant of a complex number - IMSINH() Returns the hyperbolic sine of a complex number - IMTAN() Returns the tangent of a complex number * Simplified the parseComplex() method in the PhpOffice\PhpSpreadsheet\Calculation\Engineering class, using Complex\Complex; and docblock flagged as deprecated --- CHANGELOG.md | 20 +- composer.json | 3 +- composer.lock | 97 +++- docs/references/function-list-by-category.md | 8 + docs/references/function-list-by-name.md | 8 + .../Calculation/Calculation.php | 40 ++ .../Calculation/Engineering.php | 496 ++++++++---------- .../Calculation/functionlist.txt | 8 + .../Calculation/EngineeringTest.php | 335 ++++++++++-- tests/PhpSpreadsheetTests/Custom/Complex.php | 126 ----- .../Custom/ComplexAssert.php | 45 +- tests/data/Calculation/Engineering/IMCOSH.php | 112 ++++ tests/data/Calculation/Engineering/IMCOT.php | 112 ++++ tests/data/Calculation/Engineering/IMCSC.php | 112 ++++ tests/data/Calculation/Engineering/IMCSCH.php | 112 ++++ tests/data/Calculation/Engineering/IMDIV.php | 15 - tests/data/Calculation/Engineering/IMREAL.php | 2 +- tests/data/Calculation/Engineering/IMSEC.php | 112 ++++ tests/data/Calculation/Engineering/IMSECH.php | 112 ++++ tests/data/Calculation/Engineering/IMSINH.php | 112 ++++ tests/data/Calculation/Engineering/IMSQRT.php | 6 +- tests/data/Calculation/Engineering/IMSUB.php | 15 - tests/data/Calculation/Engineering/IMTAN.php | 112 ++++ 23 files changed, 1598 insertions(+), 522 deletions(-) delete mode 100644 tests/PhpSpreadsheetTests/Custom/Complex.php create mode 100644 tests/data/Calculation/Engineering/IMCOSH.php create mode 100644 tests/data/Calculation/Engineering/IMCOT.php create mode 100644 tests/data/Calculation/Engineering/IMCSC.php create mode 100644 tests/data/Calculation/Engineering/IMCSCH.php create mode 100644 tests/data/Calculation/Engineering/IMSEC.php create mode 100644 tests/data/Calculation/Engineering/IMSECH.php create mode 100644 tests/data/Calculation/Engineering/IMSINH.php create mode 100644 tests/data/Calculation/Engineering/IMTAN.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c96eee51..c2460fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Support workbook view attributes for Xlsx format - [#523](https://github.com/PHPOffice/PhpSpreadsheet/issues/523) - Read and write hyperlink for drawing image - [#490](https://github.com/PHPOffice/PhpSpreadsheet/pull/490) - Added calculation engine support for the new bitwise functions that were added in MS Excel 2013 - - BITAND() Returns a Bitwise 'And' of two numbers - - BITOR() Returns a Bitwise 'Or' of two number - - BITXOR() Returns a Bitwise 'Exclusive Or' of two numbers - - BITLSHIFT() Returns a number shifted left by a specified number of bits - - BITRSHIFT() Returns a number shifted right by a specified number of bits + - BITAND() Returns a Bitwise 'And' of two numbers + - BITOR() Returns a Bitwise 'Or' of two number + - BITXOR() Returns a Bitwise 'Exclusive Or' of two numbers + - BITLSHIFT() Returns a number shifted left by a specified number of bits + - BITRSHIFT() Returns a number shifted right by a specified number of bits - Added calculation engine support for other new functions that were added in MS Excel 2013 and MS Excel 2016 - Text Functions - CONCAT() Synonym for CONCATENATE() @@ -46,6 +46,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - COTH() Returns the hyperbolic cotangent of an angle - ACOT() Returns the cotangent of an angle - ACOTH() Returns the hyperbolic cotangent of an angle +- Refactored Complex Engineering Functions to use external complex number library +- Added calculation engine support for the new complex number functions that were added in MS Excel 2013 + - IMCOSH() Returns the hyperbolic cosine of a complex number + - IMCOT() Returns the cotangent of a complex number + - IMCSC() Returns the cosecant of a complex number + - IMCSCH() Returns the hyperbolic cosecant of a complex number + - IMSEC() Returns the secant of a complex number + - IMSECH() Returns the hyperbolic secant of a complex number + - IMSINH() Returns the hyperbolic sine of a complex number + - IMTAN() Returns the tangent of a complex number ### Fixed diff --git a/composer.json b/composer.json index 1c033bb8..96cc5225 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,8 @@ "ext-xmlwriter": "*", "ext-zip": "*", "ext-zlib": "*", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^1.0", + "markbaker/complex": "^1.4.1" }, "require-dev": { "tecnickcom/tcpdf": "^6.2", diff --git a/composer.lock b/composer.lock index 29914657..ac3d4675 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,103 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "e61a906bd83393400add286703f10557", + "content-hash": "1aba55e3ac36d8d5015f9b3193f93c23", "packages": [ + { + "name": "markbaker/complex", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "615f5443473cf37729666e2354fd8dfa2cb48e91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/615f5443473cf37729666e2354fd8dfa2cb48e91", + "reference": "615f5443473cf37729666e2354fd8dfa2cb48e91", + "shasum": "" + }, + "require": { + "php": "^5.6.0|^7.0.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "2.*", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^4.8.35|^5.4.0", + "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "^3.1.1", + "wimg/php-compatibility": "^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + }, + "files": [ + "classes/src/functions/abs.php", + "classes/src/functions/acos.php", + "classes/src/functions/acosh.php", + "classes/src/functions/acot.php", + "classes/src/functions/acoth.php", + "classes/src/functions/acsc.php", + "classes/src/functions/acsch.php", + "classes/src/functions/argument.php", + "classes/src/functions/asec.php", + "classes/src/functions/asech.php", + "classes/src/functions/asin.php", + "classes/src/functions/asinh.php", + "classes/src/functions/atan.php", + "classes/src/functions/atanh.php", + "classes/src/functions/conjugate.php", + "classes/src/functions/cos.php", + "classes/src/functions/cosh.php", + "classes/src/functions/cot.php", + "classes/src/functions/coth.php", + "classes/src/functions/csc.php", + "classes/src/functions/csch.php", + "classes/src/functions/exp.php", + "classes/src/functions/inverse.php", + "classes/src/functions/ln.php", + "classes/src/functions/log2.php", + "classes/src/functions/log10.php", + "classes/src/functions/negative.php", + "classes/src/functions/pow.php", + "classes/src/functions/rho.php", + "classes/src/functions/sec.php", + "classes/src/functions/sech.php", + "classes/src/functions/sin.php", + "classes/src/functions/sinh.php", + "classes/src/functions/sqrt.php", + "classes/src/functions/tan.php", + "classes/src/functions/tanh.php", + "classes/src/functions/theta.php", + "classes/src/operations/add.php", + "classes/src/operations/subtract.php", + "classes/src/operations/multiply.php", + "classes/src/operations/divideby.php", + "classes/src/operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "time": "2018-07-24T19:47:28+00:00" + }, { "name": "psr/simple-cache", "version": "1.0.0", diff --git a/docs/references/function-list-by-category.md b/docs/references/function-list-by-category.md index 07f64f76..185bf4c0 100644 --- a/docs/references/function-list-by-category.md +++ b/docs/references/function-list-by-category.md @@ -86,6 +86,10 @@ IMAGINARY | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMAGINA IMARGUMENT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMARGUMENT IMCONJUGATE | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCONJUGATE IMCOS | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOS +IMCOSH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOSH +IMCOT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOT +IMCSC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSC +IMCSCH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSCH IMDIV | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMDIV IMEXP | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMEXP IMLN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLN @@ -94,10 +98,14 @@ IMLOG2 | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLOG2 IMPOWER | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPOWER IMPRODUCT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPRODUCT IMREAL | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMREAL +IMSEC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSEC +IMSECH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSECH IMSIN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSIN +IMSINH | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSINH IMSQRT | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSQRT IMSUB | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUB IMSUM | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUM +IMTAN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMTAN OCT2BIN | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTOBIN OCT2DEC | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTODEC OCT2HEX | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::OCTTOHEX diff --git a/docs/references/function-list-by-name.md b/docs/references/function-list-by-name.md index 3602f962..a8caca6c 100644 --- a/docs/references/function-list-by-name.md +++ b/docs/references/function-list-by-name.md @@ -206,6 +206,10 @@ IMAGINARY | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet IMARGUMENT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMARGUMENT IMCONJUGATE | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCONJUGATE IMCOS | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOS +IMCOSH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOSH +IMCOT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCOT +IMCSC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSC +IMCSCH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMCSCH IMDIV | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMDIV IMEXP | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMEXP IMLN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMLN @@ -214,10 +218,14 @@ IMLOG2 | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet IMPOWER | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPOWER IMPRODUCT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMPRODUCT IMREAL | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMREAL +IMSEC | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSEC +IMSECH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSECH IMSIN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSIN +IMSINH | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSINH IMSQRT | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSQRT IMSUB | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUB IMSUM | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMSUM +IMTAN | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering::IMTAN INDEX | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::INDEX INDIRECT | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::INDIRECT INFO | CATEGORY_INFORMATION | **Not yet Implemented** diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index f370e36c..79b98b95 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -1038,6 +1038,26 @@ class Calculation 'functionCall' => [Engineering::class, 'IMCOS'], 'argumentCount' => '1', ], + 'IMCOSH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMCOSH'], + 'argumentCount' => '1', + ], + 'IMCOT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMCOT'], + 'argumentCount' => '1', + ], + 'IMCSC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMCSC'], + 'argumentCount' => '1', + ], + 'IMCSCH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMCSCH'], + 'argumentCount' => '1', + ], 'IMDIV' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering::class, 'IMDIV'], @@ -1078,11 +1098,26 @@ class Calculation 'functionCall' => [Engineering::class, 'IMREAL'], 'argumentCount' => '1', ], + 'IMSEC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMSEC'], + 'argumentCount' => '1', + ], + 'IMSECH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMSECH'], + 'argumentCount' => '1', + ], 'IMSIN' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering::class, 'IMSIN'], 'argumentCount' => '1', ], + 'IMSINH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMSINH'], + 'argumentCount' => '1', + ], 'IMSQRT' => [ 'category' => Category::CATEGORY_ENGINEERING, 'functionCall' => [Engineering::class, 'IMSQRT'], @@ -1098,6 +1133,11 @@ class Calculation 'functionCall' => [Engineering::class, 'IMSUM'], 'argumentCount' => '1+', ], + 'IMTAN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'IMTAN'], + 'argumentCount' => '1', + ], 'INDEX' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [LookupRef::class, 'INDEX'], diff --git a/src/PhpSpreadsheet/Calculation/Engineering.php b/src/PhpSpreadsheet/Calculation/Engineering.php index 689f334e..3f1b48bd 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering.php +++ b/src/PhpSpreadsheet/Calculation/Engineering.php @@ -2,6 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +use Complex\Complex; +use Complex\Exception as ComplexException; + class Engineering { /** @@ -718,83 +721,23 @@ class Engineering * * Parses a complex number into its real and imaginary parts, and an I or J suffix * + * @deprecated 2.0.0 No longer used by internal code. Please use the Complex\Complex class instead + * * @param string $complexNumber The complex number * - * @return string[] Indexed on "real", "imaginary" and "suffix" + * @return mixed[] Indexed on "real", "imaginary" and "suffix" */ public static function parseComplex($complexNumber) { - $workString = (string) $complexNumber; - - $realNumber = $imaginary = 0; - // Extract the suffix, if there is one - $suffix = substr($workString, -1); - if (!is_numeric($suffix)) { - $workString = substr($workString, 0, -1); - } else { - $suffix = ''; - } - - // Split the input into its Real and Imaginary components - $leadingSign = 0; - if (strlen($workString) > 0) { - $leadingSign = (($workString[0] == '+') || ($workString[0] == '-')) ? 1 : 0; - } - $power = ''; - $realNumber = strtok($workString, '+-'); - if (strtoupper(substr($realNumber, -1)) == 'E') { - $power = strtok('+-'); - ++$leadingSign; - } - - $realNumber = substr($workString, 0, strlen($realNumber) + strlen($power) + $leadingSign); - - if ($suffix != '') { - $imaginary = substr($workString, strlen($realNumber)); - - if (($imaginary == '') && (($realNumber == '') || ($realNumber == '+') || ($realNumber == '-'))) { - $imaginary = $realNumber . '1'; - $realNumber = '0'; - } elseif ($imaginary == '') { - $imaginary = $realNumber; - $realNumber = '0'; - } elseif (($imaginary == '+') || ($imaginary == '-')) { - $imaginary .= '1'; - } - } + $complex = new Complex($complexNumber); return [ - 'real' => $realNumber, - 'imaginary' => $imaginary, - 'suffix' => $suffix, + 'real' => $complex->getReal(), + 'imaginary' => $complex->getImaginary(), + 'suffix' => $complex->getSuffix(), ]; } - /** - * Cleans the leading characters in a complex number string. - * - * @param string $complexNumber The complex number to clean - * - * @return string The "cleaned" complex number - */ - private static function cleanComplex($complexNumber) - { - if ($complexNumber[0] == '+') { - $complexNumber = substr($complexNumber, 1); - } - if ($complexNumber[0] == '0') { - $complexNumber = substr($complexNumber, 1); - } - if ($complexNumber[0] == '.') { - $complexNumber = '0' . $complexNumber; - } - if ($complexNumber[0] == '+') { - $complexNumber = substr($complexNumber, 1); - } - - return $complexNumber; - } - /** * Formats a number base string value with leading zeroes. * @@ -1745,10 +1688,10 @@ class Engineering /** * COMPLEX. * - * Converts real and imaginary coefficients into a complex number of the form x + yi or x + yj. + * Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj. * * Excel Function: - * COMPLEX(realNumber,imaginary[,places]) + * COMPLEX(realNumber,imaginary[,suffix]) * * @category Engineering Functions * @@ -1768,34 +1711,9 @@ class Engineering if (((is_numeric($realNumber)) && (is_numeric($imaginary))) && (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) ) { - $realNumber = (float) $realNumber; - $imaginary = (float) $imaginary; + $complex = new Complex($realNumber, $imaginary, $suffix); - if ($suffix == '') { - $suffix = 'i'; - } - if ($realNumber == 0.0) { - if ($imaginary == 0.0) { - return (string) '0'; - } elseif ($imaginary == 1.0) { - return (string) $suffix; - } elseif ($imaginary == -1.0) { - return (string) '-' . $suffix; - } - - return (string) $imaginary . $suffix; - } elseif ($imaginary == 0.0) { - return (string) $realNumber; - } elseif ($imaginary == 1.0) { - return (string) $realNumber . '+' . $suffix; - } elseif ($imaginary == -1.0) { - return (string) $realNumber . '-' . $suffix; - } - if ($imaginary > 0) { - $imaginary = (string) '+' . $imaginary; - } - - return (string) $realNumber . $imaginary . $suffix; + return (string) $complex; } return Functions::VALUE(); @@ -1820,9 +1738,7 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - return $parsedComplex['imaginary']; + return (new Complex($complexNumber))->getImaginary(); } /** @@ -1843,9 +1759,7 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - return $parsedComplex['real']; + return (new Complex($complexNumber))->getReal(); } /** @@ -1864,12 +1778,7 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - return sqrt( - ($parsedComplex['real'] * $parsedComplex['real']) + - ($parsedComplex['imaginary'] * $parsedComplex['imaginary']) - ); + return (new Complex($complexNumber))->abs(); } /** @@ -1883,27 +1792,18 @@ class Engineering * * @param string $complexNumber the complex number for which you want the argument theta * - * @return float + * @return float|string */ public static function IMARGUMENT($complexNumber) { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - if ($parsedComplex['real'] == 0.0) { - if ($parsedComplex['imaginary'] == 0.0) { - return Functions::DIV0(); - } elseif ($parsedComplex['imaginary'] < 0.0) { - return M_PI / -2; - } - return M_PI / 2; - } elseif ($parsedComplex['real'] > 0.0) { - return atan($parsedComplex['imaginary'] / $parsedComplex['real']); - } elseif ($parsedComplex['imaginary'] < 0.0) { - return 0 - (M_PI - atan(abs($parsedComplex['imaginary']) / abs($parsedComplex['real']))); + $complex = new Complex($complexNumber); + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::DIV0(); } - return M_PI - atan($parsedComplex['imaginary'] / abs($parsedComplex['real'])); + return $complex->argument(); } /** @@ -1922,19 +1822,7 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - if ($parsedComplex['imaginary'] == 0.0) { - return $parsedComplex['real']; - } - - return self::cleanComplex( - self::COMPLEX( - $parsedComplex['real'], - 0 - $parsedComplex['imaginary'], - $parsedComplex['suffix'] - ) - ); + return (string) (new Complex($complexNumber))->conjugate(); } /** @@ -1953,19 +1841,83 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); + return (string) (new Complex($complexNumber))->cos(); + } - if ($parsedComplex['imaginary'] == 0.0) { - return cos($parsedComplex['real']); - } + /** + * IMCOSH. + * + * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOSH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosine + * + * @return float|string + */ + public static function IMCOSH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); - return self::IMCONJUGATE( - self::COMPLEX( - cos($parsedComplex['real']) * cosh($parsedComplex['imaginary']), - sin($parsedComplex['real']) * sinh($parsedComplex['imaginary']), - $parsedComplex['suffix'] - ) - ); + return (string) (new Complex($complexNumber))->cosh(); + } + + /** + * IMCOT. + * + * Returns the cotangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cotangent + * + * @return float|string + */ + public static function IMCOT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + return (string) (new Complex($complexNumber))->cot(); + } + + /** + * IMCSC. + * + * Returns the cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSC(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cosecant + * + * @return float|string + */ + public static function IMCSC($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + return (string) (new Complex($complexNumber))->csc(); + } + + /** + * IMCSCH. + * + * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSCH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosecant + * + * @return float|string + */ + public static function IMCSCH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + return (string) (new Complex($complexNumber))->csch(); } /** @@ -1984,17 +1936,83 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); + return (string) (new Complex($complexNumber))->sin(); + } - if ($parsedComplex['imaginary'] == 0.0) { - return sin($parsedComplex['real']); - } + /** + * IMSINH. + * + * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSINH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic sine + * + * @return float|string + */ + public static function IMSINH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); - return self::COMPLEX( - sin($parsedComplex['real']) * cosh($parsedComplex['imaginary']), - cos($parsedComplex['real']) * sinh($parsedComplex['imaginary']), - $parsedComplex['suffix'] - ); + return (string) (new Complex($complexNumber))->sinh(); + } + + /** + * IMSEC. + * + * Returns the secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSEC(complexNumber) + * + * @param string $complexNumber the complex number for which you want the secant + * + * @return float|string + */ + public static function IMSEC($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + return (string) (new Complex($complexNumber))->sec(); + } + + /** + * IMSECH. + * + * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSECH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic secant + * + * @return float|string + */ + public static function IMSECH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + return (string) (new Complex($complexNumber))->sech(); + } + + /** + * IMTAN. + * + * Returns the tangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMTAN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the tangent + * + * @return float|string + */ + public static function IMTAN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + return (string) (new Complex($complexNumber))->tan(); } /** @@ -2013,22 +2031,12 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - $theta = self::IMARGUMENT($complexNumber); if ($theta === Functions::DIV0()) { return '0'; } - $d1 = cos($theta / 2); - $d2 = sin($theta / 2); - $r = sqrt(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']))); - - if ($parsedComplex['suffix'] == '') { - return self::COMPLEX($d1 * $r, $d2 * $r); - } - - return self::COMPLEX($d1 * $r, $d2 * $r, $parsedComplex['suffix']); + return (string) (new Complex($complexNumber))->sqrt(); } /** @@ -2047,20 +2055,12 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) { + $complex = new Complex($complexNumber); + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return Functions::NAN(); } - $logR = log(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']))); - $t = self::IMARGUMENT($complexNumber); - - if ($parsedComplex['suffix'] == '') { - return self::COMPLEX($logR, $t); - } - - return self::COMPLEX($logR, $t, $parsedComplex['suffix']); + return (string) (new Complex($complexNumber))->ln(); } /** @@ -2079,15 +2079,12 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) { + $complex = new Complex($complexNumber); + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return Functions::NAN(); - } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) { - return log10($parsedComplex['real']); } - return self::IMPRODUCT(log10(self::EULER), self::IMLN($complexNumber)); + return (string) (new Complex($complexNumber))->log10(); } /** @@ -2106,15 +2103,12 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) { + $complex = new Complex($complexNumber); + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return Functions::NAN(); - } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) { - return log($parsedComplex['real'], 2); } - return self::IMPRODUCT(log(self::EULER, 2), self::IMLN($complexNumber)); + return (string) (new Complex($complexNumber))->log2(); } /** @@ -2133,21 +2127,7 @@ class Engineering { $complexNumber = Functions::flattenSingleValue($complexNumber); - $parsedComplex = self::parseComplex($complexNumber); - - if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) { - return '1'; - } - - $e = exp($parsedComplex['real']); - $eX = $e * cos($parsedComplex['imaginary']); - $eY = $e * sin($parsedComplex['imaginary']); - - if ($parsedComplex['suffix'] == '') { - return self::COMPLEX($eX, $eY); - } - - return self::COMPLEX($eX, $eY, $parsedComplex['suffix']); + return (string) (new Complex($complexNumber))->exp(); } /** @@ -2172,18 +2152,7 @@ class Engineering return Functions::VALUE(); } - $parsedComplex = self::parseComplex($complexNumber); - - $r = sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])); - $rPower = pow($r, $realNumber); - $theta = self::IMARGUMENT($complexNumber) * $realNumber; - if ($theta == 0) { - return 1; - } elseif ($parsedComplex['imaginary'] == 0.0) { - return self::COMPLEX($rPower * cos($theta), $rPower * sin($theta), $parsedComplex['suffix']); - } - - return self::COMPLEX($rPower * cos($theta), $rPower * sin($theta), $parsedComplex['suffix']); + return (string) (new Complex($complexNumber))->pow($realNumber); } /** @@ -2204,32 +2173,11 @@ class Engineering $complexDividend = Functions::flattenSingleValue($complexDividend); $complexDivisor = Functions::flattenSingleValue($complexDivisor); - $parsedComplexDividend = self::parseComplex($complexDividend); - $parsedComplexDivisor = self::parseComplex($complexDivisor); - - if (($parsedComplexDividend['suffix'] != '') && ($parsedComplexDivisor['suffix'] != '') && - ($parsedComplexDividend['suffix'] != $parsedComplexDivisor['suffix']) - ) { + try { + return (string) (new Complex($complexDividend))->divideby(new Complex($complexDivisor)); + } catch (ComplexException $e) { return Functions::NAN(); } - if (($parsedComplexDividend['suffix'] != '') && ($parsedComplexDivisor['suffix'] == '')) { - $parsedComplexDivisor['suffix'] = $parsedComplexDividend['suffix']; - } - - $d1 = ($parsedComplexDividend['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['imaginary']); - $d2 = ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['real']) - ($parsedComplexDividend['real'] * $parsedComplexDivisor['imaginary']); - $d3 = ($parsedComplexDivisor['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDivisor['imaginary'] * $parsedComplexDivisor['imaginary']); - - $r = $d1 / $d3; - $i = $d2 / $d3; - - if ($i > 0.0) { - return self::cleanComplex($r . '+' . $i . $parsedComplexDivisor['suffix']); - } elseif ($i < 0.0) { - return self::cleanComplex($r . $i . $parsedComplexDivisor['suffix']); - } - - return $r; } /** @@ -2250,21 +2198,11 @@ class Engineering $complexNumber1 = Functions::flattenSingleValue($complexNumber1); $complexNumber2 = Functions::flattenSingleValue($complexNumber2); - $parsedComplex1 = self::parseComplex($complexNumber1); - $parsedComplex2 = self::parseComplex($complexNumber2); - - if ((($parsedComplex1['suffix'] != '') && ($parsedComplex2['suffix'] != '')) && - ($parsedComplex1['suffix'] != $parsedComplex2['suffix']) - ) { + try { + return (string) (new Complex($complexNumber1))->subtract(new Complex($complexNumber2)); + } catch (ComplexException $e) { return Functions::NAN(); - } elseif (($parsedComplex1['suffix'] == '') && ($parsedComplex2['suffix'] != '')) { - $parsedComplex1['suffix'] = $parsedComplex2['suffix']; } - - $d1 = $parsedComplex1['real'] - $parsedComplex2['real']; - $d2 = $parsedComplex1['imaginary'] - $parsedComplex2['imaginary']; - - return self::COMPLEX($d1, $d2, $parsedComplex1['suffix']); } /** @@ -2282,29 +2220,19 @@ class Engineering public static function IMSUM(...$complexNumbers) { // Return value - $returnValue = self::parseComplex('0'); - $activeSuffix = ''; - - // Loop through the arguments + $returnValue = new Complex(0.0); $aArgs = Functions::flattenArray($complexNumbers); - foreach ($aArgs as $arg) { - $parsedComplex = self::parseComplex($arg); - if ($activeSuffix == '') { - $activeSuffix = $parsedComplex['suffix']; - } elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) { - return Functions::NAN(); + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->add(new Complex($complex)); } - - $returnValue['real'] += $parsedComplex['real']; - $returnValue['imaginary'] += $parsedComplex['imaginary']; + } catch (ComplexException $e) { + return Functions::NAN(); } - if ($returnValue['imaginary'] == 0.0) { - $activeSuffix = ''; - } - - return self::COMPLEX($returnValue['real'], $returnValue['imaginary'], $activeSuffix); + return (string) $returnValue; } /** @@ -2322,29 +2250,19 @@ class Engineering public static function IMPRODUCT(...$complexNumbers) { // Return value - $returnValue = self::parseComplex('1'); - $activeSuffix = ''; - - // Loop through the arguments + $returnValue = new Complex(1.0); $aArgs = Functions::flattenArray($complexNumbers); - foreach ($aArgs as $arg) { - $parsedComplex = self::parseComplex($arg); - $workValue = $returnValue; - if (($parsedComplex['suffix'] != '') && ($activeSuffix == '')) { - $activeSuffix = $parsedComplex['suffix']; - } elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) { - return Functions::NAN(); + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->multiply(new Complex($complex)); } - $returnValue['real'] = ($workValue['real'] * $parsedComplex['real']) - ($workValue['imaginary'] * $parsedComplex['imaginary']); - $returnValue['imaginary'] = ($workValue['real'] * $parsedComplex['imaginary']) + ($workValue['imaginary'] * $parsedComplex['real']); + } catch (ComplexException $e) { + return Functions::NAN(); } - if ($returnValue['imaginary'] == 0.0) { - $activeSuffix = ''; - } - - return self::COMPLEX($returnValue['real'], $returnValue['imaginary'], $activeSuffix); + return (string) $returnValue; } /** diff --git a/src/PhpSpreadsheet/Calculation/functionlist.txt b/src/PhpSpreadsheet/Calculation/functionlist.txt index ef54ad62..7043f769 100644 --- a/src/PhpSpreadsheet/Calculation/functionlist.txt +++ b/src/PhpSpreadsheet/Calculation/functionlist.txt @@ -163,6 +163,10 @@ IMAGINARY IMARGUMENT IMCONJUGATE IMCOS +IMCOSH +IMCOT +IMCSC +IMCSCH IMEXP IMLN IMLOG10 @@ -170,10 +174,14 @@ IMLOG2 IMPOWER IMPRODUCT IMREAL +IMSEC +IMSECH IMSIN +IMSINH IMSQRT IMSUB IMSUM +IMTAN INDEX INDIRECT INFO diff --git a/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php b/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php index 44d6dd31..09da20c9 100644 --- a/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php @@ -14,6 +14,10 @@ class EngineeringTest extends TestCase */ protected $complexAssert; + const BESSEL_PRECISION = 1E-8; + const COMPLEX_PRECISION = 1E-8; + const ERF_PRECISION = 1E-12; + public function setUp() { $this->complexAssert = new ComplexAssert(); @@ -33,7 +37,7 @@ class EngineeringTest extends TestCase public function testBESSELI($expectedResult, ...$args) { $result = Engineering::BESSELI(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + self::assertEquals($expectedResult, $result, null, self::BESSEL_PRECISION); } public function providerBESSELI() @@ -49,7 +53,7 @@ class EngineeringTest extends TestCase public function testBESSELJ($expectedResult, ...$args) { $result = Engineering::BESSELJ(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + self::assertEquals($expectedResult, $result, null, self::BESSEL_PRECISION); } public function providerBESSELJ() @@ -65,7 +69,7 @@ class EngineeringTest extends TestCase public function testBESSELK($expectedResult, ...$args) { $result = Engineering::BESSELK(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + self::assertEquals($expectedResult, $result, null, self::BESSEL_PRECISION); } public function providerBESSELK() @@ -81,7 +85,7 @@ class EngineeringTest extends TestCase public function testBESSELY($expectedResult, ...$args) { $result = Engineering::BESSELY(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + self::assertEquals($expectedResult, $result, null, self::BESSEL_PRECISION); } public function providerBESSELY() @@ -89,6 +93,24 @@ class EngineeringTest extends TestCase return require 'data/Calculation/Engineering/BESSELY.php'; } + /** + * @dataProvider providerCOMPLEX + * + * @param mixed $expectedResult + */ + public function testParseComplex() + { + list($real, $imaginary, $suffix) = [1.23e-4, 5.67e+8, 'j']; + + $result = Engineering::parseComplex('1.23e-4+5.67e+8j'); + $this->assertArrayHasKey('real', $result); + $this->assertEquals($real, $result['real']); + $this->assertArrayHasKey('imaginary', $result); + $this->assertEquals($imaginary, $result['imaginary']); + $this->assertArrayHasKey('suffix', $result); + $this->assertEquals($suffix, $result['suffix']); + } + /** * @dataProvider providerCOMPLEX * @@ -109,11 +131,12 @@ class EngineeringTest extends TestCase * @dataProvider providerIMAGINARY * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMAGINARY($expectedResult, ...$args) + public function testIMAGINARY($expectedResult, $value) { - $result = Engineering::IMAGINARY(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + $result = Engineering::IMAGINARY($value); + self::assertEquals($expectedResult, $result, null, self::COMPLEX_PRECISION); } public function providerIMAGINARY() @@ -125,11 +148,12 @@ class EngineeringTest extends TestCase * @dataProvider providerIMREAL * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMREAL($expectedResult, ...$args) + public function testIMREAL($expectedResult, $value) { - $result = Engineering::IMREAL(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + $result = Engineering::IMREAL($value); + self::assertEquals($expectedResult, $result, null, self::COMPLEX_PRECISION); } public function providerIMREAL() @@ -141,11 +165,12 @@ class EngineeringTest extends TestCase * @dataProvider providerIMABS * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMABS($expectedResult, ...$args) + public function testIMABS($expectedResult, $value) { - $result = Engineering::IMABS(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + $result = Engineering::IMABS($value); + self::assertEquals($expectedResult, $result, null, self::COMPLEX_PRECISION); } public function providerIMABS() @@ -157,11 +182,12 @@ class EngineeringTest extends TestCase * @dataProvider providerIMARGUMENT * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMARGUMENT($expectedResult, ...$args) + public function testIMARGUMENT($expectedResult, $value) { - $result = Engineering::IMARGUMENT(...$args); - self::assertEquals($expectedResult, $result, null, 1E-8); + $result = Engineering::IMARGUMENT($value); + self::assertEquals($expectedResult, $result, null, self::COMPLEX_PRECISION); } public function providerIMARGUMENT() @@ -173,11 +199,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMCONJUGATE * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMCONJUGATE($expectedResult, ...$args) + public function testIMCONJUGATE($expectedResult, $value) { - $result = Engineering::IMCONJUGATE(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMCONJUGATE($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMCONJUGATE() @@ -189,11 +219,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMCOS * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMCOS($expectedResult, ...$args) + public function testIMCOS($expectedResult, $value) { - $result = Engineering::IMCOS(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMCOS($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMCOS() @@ -201,6 +235,126 @@ class EngineeringTest extends TestCase return require 'data/Calculation/Engineering/IMCOS.php'; } + /** + * @dataProvider providerIMCOSH + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMCOSH($expectedResult, $value) + { + $result = Engineering::IMCOSH($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMCOSH() + { + return require 'data/Calculation/Engineering/IMCOSH.php'; + } + + /** + * @dataProvider providerIMCOT + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMCOT($expectedResult, $value) + { + $result = Engineering::IMCOT($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMCOT() + { + return require 'data/Calculation/Engineering/IMCOT.php'; + } + + /** + * @dataProvider providerIMCSC + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMCSC($expectedResult, $value) + { + $result = Engineering::IMCSC($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMCSC() + { + return require 'data/Calculation/Engineering/IMCSC.php'; + } + + /** + * @dataProvider providerIMCSCH + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMCSCH($expectedResult, $value) + { + $result = Engineering::IMCSCH($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMCSCH() + { + return require 'data/Calculation/Engineering/IMCSCH.php'; + } + + /** + * @dataProvider providerIMSEC + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMSEC($expectedResult, $value) + { + $result = Engineering::IMSEC($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMSEC() + { + return require 'data/Calculation/Engineering/IMSEC.php'; + } + + /** + * @dataProvider providerIMSECH + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMSECH($expectedResult, $value) + { + $result = Engineering::IMSECH($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMSECH() + { + return require 'data/Calculation/Engineering/IMSECH.php'; + } + /** * @dataProvider providerIMDIV * @@ -208,10 +362,11 @@ class EngineeringTest extends TestCase */ public function testIMDIV($expectedResult, ...$args) { - $this->markTestIncomplete('TODO: This test should be fixed'); - $result = Engineering::IMDIV(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMDIV() @@ -223,11 +378,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMEXP * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMEXP($expectedResult, ...$args) + public function testIMEXP($expectedResult, $value) { - $result = Engineering::IMEXP(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMEXP($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMEXP() @@ -239,11 +398,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMLN * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMLN($expectedResult, ...$args) + public function testIMLN($expectedResult, $value) { - $result = Engineering::IMLN(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMLN($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMLN() @@ -255,11 +418,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMLOG2 * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMLOG2($expectedResult, ...$args) + public function testIMLOG2($expectedResult, $value) { - $result = Engineering::IMLOG2(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMLOG2($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMLOG2() @@ -271,11 +438,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMLOG10 * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMLOG10($expectedResult, ...$args) + public function testIMLOG10($expectedResult, $value) { - $result = Engineering::IMLOG10(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMLOG10($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMLOG10() @@ -290,10 +461,11 @@ class EngineeringTest extends TestCase */ public function testIMPOWER($expectedResult, ...$args) { - $this->markTestIncomplete('TODO: This test should be fixed'); - $result = Engineering::IMPOWER(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMPOWER() @@ -309,7 +481,10 @@ class EngineeringTest extends TestCase public function testIMPRODUCT($expectedResult, ...$args) { $result = Engineering::IMPRODUCT(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMPRODUCT() @@ -321,11 +496,15 @@ class EngineeringTest extends TestCase * @dataProvider providerIMSIN * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMSIN($expectedResult, ...$args) + public function testIMSIN($expectedResult, $value) { - $result = Engineering::IMSIN(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMSIN($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMSIN() @@ -333,15 +512,59 @@ class EngineeringTest extends TestCase return require 'data/Calculation/Engineering/IMSIN.php'; } + /** + * @dataProvider providerIMSINH + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMSINH($expectedResult, $value) + { + $result = Engineering::IMSINH($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMSINH() + { + return require 'data/Calculation/Engineering/IMSINH.php'; + } + + /** + * @dataProvider providerIMTAN + * + * @param mixed $expectedResult + * @param mixed $value + */ + public function testIMTAN($expectedResult, $value) + { + $result = Engineering::IMTAN($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); + } + + public function providerIMTAN() + { + return require 'data/Calculation/Engineering/IMTAN.php'; + } + /** * @dataProvider providerIMSQRT * * @param mixed $expectedResult + * @param mixed $value */ - public function testIMSQRT($expectedResult, ...$args) + public function testIMSQRT($expectedResult, $value) { - $result = Engineering::IMSQRT(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + $result = Engineering::IMSQRT($value); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMSQRT() @@ -356,10 +579,11 @@ class EngineeringTest extends TestCase */ public function testIMSUB($expectedResult, ...$args) { - $this->markTestIncomplete('TODO: This test should be fixed'); - $result = Engineering::IMSUB(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMSUB() @@ -375,7 +599,10 @@ class EngineeringTest extends TestCase public function testIMSUM($expectedResult, ...$args) { $result = Engineering::IMSUM(...$args); - self::assertTrue($this->complexAssert->assertComplexEquals($expectedResult, $result, 1E-8), $this->complexAssert->getErrorMessage()); + self::assertTrue( + $this->complexAssert->assertComplexEquals($expectedResult, $result, self::COMPLEX_PRECISION), + $this->complexAssert->getErrorMessage() + ); } public function providerIMSUM() @@ -391,7 +618,7 @@ class EngineeringTest extends TestCase public function testERF($expectedResult, ...$args) { $result = Engineering::ERF(...$args); - self::assertEquals($expectedResult, $result, null, 1E-12); + self::assertEquals($expectedResult, $result, null, self::ERF_PRECISION); } public function providerERF() @@ -407,7 +634,7 @@ class EngineeringTest extends TestCase public function testERFPRECISE($expectedResult, ...$args) { $result = Engineering::ERFPRECISE(...$args); - self::assertEquals($expectedResult, $result, null, 1E-12); + self::assertEquals($expectedResult, $result, null, self::ERF_PRECISION); } public function providerERFPRECISE() @@ -423,7 +650,7 @@ class EngineeringTest extends TestCase public function testERFC($expectedResult, ...$args) { $result = Engineering::ERFC(...$args); - self::assertEquals($expectedResult, $result, null, 1E-12); + self::assertEquals($expectedResult, $result, null, self::ERF_PRECISION); } public function providerERFC() diff --git a/tests/PhpSpreadsheetTests/Custom/Complex.php b/tests/PhpSpreadsheetTests/Custom/Complex.php deleted file mode 100644 index d83af1ba..00000000 --- a/tests/PhpSpreadsheetTests/Custom/Complex.php +++ /dev/null @@ -1,126 +0,0 @@ -realPart = (float) $realPart; - $this->imaginaryPart = (float) $imaginaryPart; - $this->suffix = strtolower($suffix); - } - - public function getReal() - { - return $this->realPart; - } - - public function getImaginary() - { - return $this->imaginaryPart; - } - - public function getSuffix() - { - return $this->suffix; - } - - public function __toString() - { - $str = ''; - if ($this->imaginaryPart != 0.0) { - if (abs($this->imaginaryPart) != 1.0) { - $str .= $this->imaginaryPart . $this->suffix; - } else { - $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; - } - } - if ($this->realPart != 0.0) { - if (($str) && ($this->imaginaryPart > 0.0)) { - $str = '+' . $str; - } - $str = $this->realPart . $str; - } - if (!$str) { - $str = '0.0'; - } - - return $str; - } -} diff --git a/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php b/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php index 112d4336..762b2886 100644 --- a/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php +++ b/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php @@ -2,20 +2,41 @@ namespace PhpOffice\PhpSpreadsheetTests\Custom; +use Complex\Complex; + class ComplexAssert { private $errorMessage = ''; + private function testExpectedExceptions($expected, $actual) + { + // Expecting an error, so we do a straight string comparison + if ($expected === $actual) { + return true; + } elseif ($expected === INF && $actual === 'INF') { + return true; + } + $this->errorMessage = 'Expected Error: ' . $actual . ' !== ' . $expected; + + return false; + } + + private function adjustDelta($expected, $actual, $delta) + { + $adjustedDelta = $delta; + + if (abs($actual) > 10 && abs($expected) > 10) { + $variance = floor(log10(abs($expected))); + $adjustedDelta *= pow(10, $variance); + } + + return $adjustedDelta > 1.0 ? 1.0 : $adjustedDelta; + } + public function assertComplexEquals($expected, $actual, $delta = 0) { - if ($expected[0] === '#') { - // Expecting an error, so we do a straight string comparison - if ($expected === $actual) { - return true; - } - $this->errorMessage = 'Expected Error: ' . $actual . ' !== ' . $expected; - - return false; + if ($expected === INF || $expected[0] === '#') { + return $this->testExpectedExceptions($expected, $actual); } $expectedComplex = new Complex($expected); @@ -31,15 +52,15 @@ class ComplexAssert return true; } - if ($actualComplex->getReal() < ($expectedComplex->getReal() - $delta) || - $actualComplex->getReal() > ($expectedComplex->getReal() + $delta)) { + $adjustedDelta = $this->adjustDelta($expectedComplex->getReal(), $actualComplex->getReal(), $delta); + if (abs($actualComplex->getReal() - $expectedComplex->getReal()) > $adjustedDelta) { $this->errorMessage = 'Mismatched Real part: ' . $actualComplex->getReal() . ' != ' . $expectedComplex->getReal(); return false; } - if ($actualComplex->getImaginary() < ($expectedComplex->getImaginary() - $delta) || - $actualComplex->getImaginary() > ($expectedComplex->getImaginary() + $delta)) { + $adjustedDelta = $this->adjustDelta($expectedComplex->getImaginary(), $actualComplex->getImaginary(), $delta); + if (abs($actualComplex->getImaginary() - $expectedComplex->getImaginary()) > $adjustedDelta) { $this->errorMessage = 'Mismatched Imaginary part: ' . $actualComplex->getImaginary() . ' != ' . $expectedComplex->getImaginary(); return false; diff --git a/tests/data/Calculation/Engineering/IMCOSH.php b/tests/data/Calculation/Engineering/IMCOSH.php new file mode 100644 index 00000000..dd394787 --- /dev/null +++ b/tests/data/Calculation/Engineering/IMCOSH.php @@ -0,0 +1,112 @@ +