From 67cdee6033af62ffdbc71607e3953bc3867aeeba Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Sun, 22 Jul 2018 22:16:34 +0100 Subject: [PATCH] Add new Bitwise Functions introduced in MS Excel 2013 (#603) * - 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 --- CHANGELOG.md | 10 +- .../Calculation/Calculation.php | 25 +++ .../Calculation/Engineering.php | 173 ++++++++++++++++++ .../Calculation/functionlist.txt | 5 + .../Calculation/EngineeringTest.php | 85 +++++++++ tests/data/Calculation/Engineering/BITAND.php | 20 ++ .../Calculation/Engineering/BITLSHIFT.php | 20 ++ tests/data/Calculation/Engineering/BITOR.php | 24 +++ .../Calculation/Engineering/BITRSHIFT.php | 16 ++ tests/data/Calculation/Engineering/BITXOR.php | 24 +++ 10 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 tests/data/Calculation/Engineering/BITAND.php create mode 100644 tests/data/Calculation/Engineering/BITLSHIFT.php create mode 100644 tests/data/Calculation/Engineering/BITOR.php create mode 100644 tests/data/Calculation/Engineering/BITRSHIFT.php create mode 100644 tests/data/Calculation/Engineering/BITXOR.php diff --git a/CHANGELOG.md b/CHANGELOG.md index dd729183..c96eee51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add excel function EXACT(value1, value2) support - [595](https://github.com/PHPOffice/PhpSpreadsheet/pull/595) - 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) -- Fix ISFORMULA() function to work with a cell reference to another worksheet -- Added calculation engine support for the new functions that were added in MS Excel 2013 and MS Excel 2016 +- 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 +- 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() - NUMBERVALUE() Converts text to a number, in a locale-independent way @@ -44,6 +49,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fix ISFORMULA() function to work with a cell reference to another worksheet - Xlsx reader crashed when reading a file with workbook protection - [#553](https://github.com/PHPOffice/PhpSpreadsheet/pull/553) - Cell formats with escaped spaces were causing incorrect date formatting - [#557](https://github.com/PHPOffice/PhpSpreadsheet/issues/557) - Could not open CSV file containing HTML fragment - [#564](https://github.com/PHPOffice/PhpSpreadsheet/issues/564) diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index f5e7b4c8..f370e36c 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -369,6 +369,31 @@ class Calculation 'functionCall' => [Statistical::class, 'BINOMDIST'], 'argumentCount' => '4', ], + 'BITAND' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'BITAND'], + 'argumentCount' => '2', + ], + 'BITOR' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'BITOR'], + 'argumentCount' => '2', + ], + 'BITXOR' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'BITOR'], + 'argumentCount' => '2', + ], + 'BITLSHIFT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'BITLSHIFT'], + 'argumentCount' => '2', + ], + 'BITRSHIFT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering::class, 'BITRSHIFT'], + 'argumentCount' => '2', + ], 'CEILING' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => [MathTrig::class, 'CEILING'], diff --git a/src/PhpSpreadsheet/Calculation/Engineering.php b/src/PhpSpreadsheet/Calculation/Engineering.php index e16464b9..689f334e 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering.php +++ b/src/PhpSpreadsheet/Calculation/Engineering.php @@ -2423,6 +2423,179 @@ class Engineering return self::$twoSqrtPi * $sum; } + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @throws Exception + * + * @return int + */ + private static function validateBitwiseArgument($value) + { + $value = Functions::flattenSingleValue($value); + + if (is_int($value)) { + return $value; + } elseif (is_numeric($value)) { + if ($value == (int) ($value)) { + $value = (int) ($value); + if (($value > pow(2, 48) - 1) || ($value < 0)) { + throw new Exception(Functions::NAN()); + } + + return $value; + } + + throw new Exception(Functions::NAN()); + } + + throw new Exception(Functions::VALUE()); + } + + /** + * BITAND. + * + * Returns the bitwise AND of two integer values. + * + * Excel Function: + * BITAND(number1, number2) + * + * @category Engineering Functions + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITAND($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + return $number1 & $number2; + } + + /** + * BITOR. + * + * Returns the bitwise OR of two integer values. + * + * Excel Function: + * BITOR(number1, number2) + * + * @category Engineering Functions + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITOR($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + return $number1 | $number2; + } + + /** + * BITXOR. + * + * Returns the bitwise XOR of two integer values. + * + * Excel Function: + * BITXOR(number1, number2) + * + * @category Engineering Functions + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITXOR($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + return $number1 ^ $number2; + } + + /** + * BITLSHIFT. + * + * Returns the number value shifted left by shift_amount bits. + * + * Excel Function: + * BITLSHIFT(number, shift_amount) + * + * @category Engineering Functions + * + * @param int $number + * @param int $shiftAmount + * + * @return int|string + */ + public static function BITLSHIFT($number, $shiftAmount) + { + try { + $number = self::validateBitwiseArgument($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + $shiftAmount = Functions::flattenSingleValue($shiftAmount); + + $result = $number << $shiftAmount; + if ($result > pow(2, 48) - 1) { + return Functions::NAN(); + } + + return $result; + } + + /** + * BITRSHIFT. + * + * Returns the number value shifted right by shift_amount bits. + * + * Excel Function: + * BITRSHIFT(number, shift_amount) + * + * @category Engineering Functions + * + * @param int $number + * @param int $shiftAmount + * + * @return int|string + */ + public static function BITRSHIFT($number, $shiftAmount) + { + try { + $number = self::validateBitwiseArgument($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + $shiftAmount = Functions::flattenSingleValue($shiftAmount); + + return $number >> $shiftAmount; + } + /** * ERF. * diff --git a/src/PhpSpreadsheet/Calculation/functionlist.txt b/src/PhpSpreadsheet/Calculation/functionlist.txt index c8dcfd50..ef54ad62 100644 --- a/src/PhpSpreadsheet/Calculation/functionlist.txt +++ b/src/PhpSpreadsheet/Calculation/functionlist.txt @@ -32,6 +32,11 @@ BIN2DEC BIN2HEX BIN2OCT BINOMDIST +BITAND +BITLSHIFT +BITOR +BITRSHIFT +BITXOR CEILING CELL CHAR diff --git a/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php b/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php index 341083bf..44d6dd31 100644 --- a/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/EngineeringTest.php @@ -623,6 +623,91 @@ class EngineeringTest extends TestCase return require 'data/Calculation/Engineering/OCT2HEX.php'; } + /** + * @dataProvider providerBITAND + * + * @param mixed $expectedResult + * @param mixed[] $args + */ + public function testBITAND($expectedResult, array $args) + { + $result = Engineering::BITAND(...$args); + self::assertEquals($expectedResult, $result, null); + } + + public function providerBITAND() + { + return require 'data/Calculation/Engineering/BITAND.php'; + } + + /** + * @dataProvider providerBITOR + * + * @param mixed $expectedResult + * @param mixed[] $args + */ + public function testBITOR($expectedResult, array $args) + { + $result = Engineering::BITOR(...$args); + self::assertEquals($expectedResult, $result, null); + } + + public function providerBITOR() + { + return require 'data/Calculation/Engineering/BITOR.php'; + } + + /** + * @dataProvider providerBITXOR + * + * @param mixed $expectedResult + * @param mixed[] $args + */ + public function testBITXOR($expectedResult, array $args) + { + $result = Engineering::BITXOR(...$args); + self::assertEquals($expectedResult, $result, null); + } + + public function providerBITXOR() + { + return require 'data/Calculation/Engineering/BITXOR.php'; + } + + /** + * @dataProvider providerBITLSHIFT + * + * @param mixed $expectedResult + * @param mixed[] $args + */ + public function testBITLSHIFT($expectedResult, array $args) + { + $result = Engineering::BITLSHIFT(...$args); + self::assertEquals($expectedResult, $result, null); + } + + public function providerBITLSHIFT() + { + return require 'data/Calculation/Engineering/BITLSHIFT.php'; + } + + /** + * @dataProvider providerBITRSHIFT + * + * @param mixed $expectedResult + * @param mixed[] $args + */ + public function testBITRSHIFT($expectedResult, array $args) + { + $result = Engineering::BITRSHIFT(...$args); + self::assertEquals($expectedResult, $result, null); + } + + public function providerBITRSHIFT() + { + return require 'data/Calculation/Engineering/BITRSHIFT.php'; + } + /** * @dataProvider providerDELTA * diff --git a/tests/data/Calculation/Engineering/BITAND.php b/tests/data/Calculation/Engineering/BITAND.php new file mode 100644 index 00000000..0b8ae154 --- /dev/null +++ b/tests/data/Calculation/Engineering/BITAND.php @@ -0,0 +1,20 @@ +