Extract all Base Conversion functions from the Engineering class into dedicated Base Conversion classes (#1849)

* Extract all Base Conversion functions from the Engineering class into a dedicated Convert<Base> classes extending from a common ConvertBase class

Retain the original methods in the Engineering class as stubs for BC, but deprecate them. They will be removed for PHPSpreadsheet v2

Note that unit tests still point to the Engineering class stubs; these should be modified to use the Convert<Base> classes directly when the stubs are removed

* Split out into separate base conversion classes, with a ConvertBase class for common methods
This commit is contained in:
Mark Baker 2021-02-13 11:21:16 +01:00 committed by GitHub
parent 69315f0640
commit 61d2e6dcd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 696 additions and 304 deletions

View File

@ -403,17 +403,17 @@ class Calculation
], ],
'BIN2DEC' => [ 'BIN2DEC' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'BINTODEC'], 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'],
'argumentCount' => '1', 'argumentCount' => '1',
], ],
'BIN2HEX' => [ 'BIN2HEX' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'BINTOHEX'], 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'BIN2OCT' => [ 'BIN2OCT' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'BINTOOCT'], 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'BINOMDIST' => [ 'BINOMDIST' => [
@ -814,17 +814,17 @@ class Calculation
], ],
'DEC2BIN' => [ 'DEC2BIN' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'DECTOBIN'], 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'DEC2HEX' => [ 'DEC2HEX' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'DECTOHEX'], 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'DEC2OCT' => [ 'DEC2OCT' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'DECTOOCT'], 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'DECIMAL' => [ 'DECIMAL' => [
@ -1216,17 +1216,17 @@ class Calculation
], ],
'HEX2BIN' => [ 'HEX2BIN' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'HEXTOBIN'], 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'HEX2DEC' => [ 'HEX2DEC' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'HEXTODEC'], 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'],
'argumentCount' => '1', 'argumentCount' => '1',
], ],
'HEX2OCT' => [ 'HEX2OCT' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'HEXTOOCT'], 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'HLOOKUP' => [ 'HLOOKUP' => [
@ -1840,17 +1840,17 @@ class Calculation
], ],
'OCT2BIN' => [ 'OCT2BIN' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'OCTTOBIN'], 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'OCT2DEC' => [ 'OCT2DEC' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'OCTTODEC'], 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'],
'argumentCount' => '1', 'argumentCount' => '1',
], ],
'OCT2HEX' => [ 'OCT2HEX' => [
'category' => Category::CATEGORY_ENGINEERING, 'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'OCTTOHEX'], 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'],
'argumentCount' => '1,2', 'argumentCount' => '1,2',
], ],
'ODD' => [ 'ODD' => [

View File

@ -4,9 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
use Complex\Complex; use Complex\Complex;
use Complex\Exception as ComplexException; use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\Engineering\Bessel;
use PhpOffice\PhpSpreadsheet\Calculation\Engineering\BitWise;
use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ConvertUOM;
class Engineering class Engineering
{ {
@ -37,35 +34,6 @@ class Engineering
]; ];
} }
/**
* Formats a number base string value with leading zeroes.
*
* @param string $xVal The "number" to pad
* @param int $places The length that we want to pad this value
*
* @return string The padded "number"
*/
private static function nbrConversionFormat($xVal, $places)
{
if ($places !== null) {
if (is_numeric($places)) {
$places = (int) $places;
} else {
return Functions::VALUE();
}
if ($places < 0) {
return Functions::NAN();
}
if (strlen($xVal) <= $places) {
return substr(str_pad($xVal, $places, '0', STR_PAD_LEFT), -10);
}
return Functions::NAN();
}
return substr($xVal, -10);
}
/** /**
* BESSELI. * BESSELI.
* *
@ -169,6 +137,8 @@ class Engineering
* Excel Function: * Excel Function:
* BIN2DEC(x) * BIN2DEC(x)
* *
* @Deprecated 2.0.0 Use the toDecimal() method in the Engineering\ConvertBinary class instead
*
* @param string $x The binary number (as a string) that you want to convert. The number * @param string $x The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant * cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits. * bit of number is the sign bit. The remaining 9 bits are magnitude bits.
@ -180,32 +150,7 @@ class Engineering
*/ */
public static function BINTODEC($x) public static function BINTODEC($x)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertBinary::toDecimal($x);
if (is_bool($x)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$x = (int) $x;
} else {
return Functions::VALUE();
}
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
$x = floor((float) $x);
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[01]/', $x, $out)) {
return Functions::NAN();
}
if (strlen($x) > 10) {
return Functions::NAN();
} elseif (strlen($x) == 10) {
// Two's Complement
$x = substr($x, -9);
return '-' . (512 - bindec($x));
}
return bindec($x);
} }
/** /**
@ -216,6 +161,8 @@ class Engineering
* Excel Function: * Excel Function:
* BIN2HEX(x[,places]) * BIN2HEX(x[,places])
* *
* @Deprecated 2.0.0 Use the toHex() method in the Engineering\ConvertBinary class instead
*
* @param string $x The binary number (as a string) that you want to convert. The number * @param string $x The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant * cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits. * bit of number is the sign bit. The remaining 9 bits are magnitude bits.
@ -233,33 +180,7 @@ class Engineering
*/ */
public static function BINTOHEX($x, $places = null) public static function BINTOHEX($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertBinary::toHex($x, $places);
$places = Functions::flattenSingleValue($places);
// Argument X
if (is_bool($x)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$x = (int) $x;
} else {
return Functions::VALUE();
}
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
$x = floor((float) $x);
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[01]/', $x, $out)) {
return Functions::NAN();
}
if (strlen($x) > 10) {
return Functions::NAN();
} elseif (strlen($x) == 10) {
// Two's Complement
return str_repeat('F', 8) . substr(strtoupper(dechex((int) bindec(substr($x, -9)))), -2);
}
$hexVal = (string) strtoupper(dechex((int) bindec($x)));
return self::nbrConversionFormat($hexVal, $places);
} }
/** /**
@ -270,6 +191,8 @@ class Engineering
* Excel Function: * Excel Function:
* BIN2OCT(x[,places]) * BIN2OCT(x[,places])
* *
* @Deprecated 2.0.0 Use the toOctal() method in the Engineering\ConvertBinary class instead
*
* @param string $x The binary number (as a string) that you want to convert. The number * @param string $x The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant * cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits. * bit of number is the sign bit. The remaining 9 bits are magnitude bits.
@ -287,32 +210,7 @@ class Engineering
*/ */
public static function BINTOOCT($x, $places = null) public static function BINTOOCT($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertBinary::toOctal($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$x = (int) $x;
} else {
return Functions::VALUE();
}
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
$x = floor((float) $x);
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[01]/', $x, $out)) {
return Functions::NAN();
}
if (strlen($x) > 10) {
return Functions::NAN();
} elseif (strlen($x) == 10) {
// Two's Complement
return str_repeat('7', 7) . substr(strtoupper(decoct((int) bindec(substr($x, -9)))), -3);
}
$octVal = (string) decoct((int) bindec($x));
return self::nbrConversionFormat($octVal, $places);
} }
/** /**
@ -323,6 +221,8 @@ class Engineering
* Excel Function: * Excel Function:
* DEC2BIN(x[,places]) * DEC2BIN(x[,places])
* *
* @Deprecated 2.0.0 Use the toBinary() method in the Engineering\ConvertDecimal class instead
*
* @param string $x The decimal integer you want to convert. If number is negative, * @param string $x The decimal integer you want to convert. If number is negative,
* valid place values are ignored and DEC2BIN returns a 10-character * valid place values are ignored and DEC2BIN returns a 10-character
* (10-bit) binary number in which the most significant bit is the sign * (10-bit) binary number in which the most significant bit is the sign
@ -344,34 +244,7 @@ class Engineering
*/ */
public static function DECTOBIN($x, $places = null) public static function DECTOBIN($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertDecimal::toBinary($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$x = (int) $x;
} else {
return Functions::VALUE();
}
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) {
return Functions::VALUE();
}
$x = (int) floor((float) $x);
if ($x < -512 || $x > 511) {
return Functions::NAN();
}
$r = decbin($x);
// Two's Complement
$r = substr($r, -10);
if (strlen($r) >= 11) {
return Functions::NAN();
}
return self::nbrConversionFormat($r, $places);
} }
/** /**
@ -382,6 +255,8 @@ class Engineering
* Excel Function: * Excel Function:
* DEC2HEX(x[,places]) * DEC2HEX(x[,places])
* *
* @Deprecated 2.0.0 Use the toHex() method in the Engineering\ConvertDecimal class instead
*
* @param string $x The decimal integer you want to convert. If number is negative, * @param string $x The decimal integer you want to convert. If number is negative,
* places is ignored and DEC2HEX returns a 10-character (40-bit) * places is ignored and DEC2HEX returns a 10-character (40-bit)
* hexadecimal number in which the most significant bit is the sign * hexadecimal number in which the most significant bit is the sign
@ -403,28 +278,7 @@ class Engineering
*/ */
public static function DECTOHEX($x, $places = null) public static function DECTOHEX($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertDecimal::toHex($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$x = (int) $x;
} else {
return Functions::VALUE();
}
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) {
return Functions::VALUE();
}
$x = (int) floor((float) $x);
$r = strtoupper(dechex($x));
if (strlen($r) == 8) {
// Two's Complement
$r = 'FF' . $r;
}
return self::nbrConversionFormat($r, $places);
} }
/** /**
@ -435,6 +289,8 @@ class Engineering
* Excel Function: * Excel Function:
* DEC2OCT(x[,places]) * DEC2OCT(x[,places])
* *
* @Deprecated 2.0.0 Use the toOctal() method in the Engineering\ConvertDecimal class instead
*
* @param string $x The decimal integer you want to convert. If number is negative, * @param string $x The decimal integer you want to convert. If number is negative,
* places is ignored and DEC2OCT returns a 10-character (30-bit) * places is ignored and DEC2OCT returns a 10-character (30-bit)
* octal number in which the most significant bit is the sign bit. * octal number in which the most significant bit is the sign bit.
@ -456,29 +312,7 @@ class Engineering
*/ */
public static function DECTOOCT($x, $places = null) public static function DECTOOCT($x, $places = null)
{ {
$xorig = $x; return Engineering\ConvertDecimal::toOctal($x, $places);
$x = Functions::flattenSingleValue($x);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$x = (int) $x;
} else {
return Functions::VALUE();
}
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) {
return Functions::VALUE();
}
$x = (int) floor((float) $x);
$r = decoct($x);
if (strlen($r) == 11) {
// Two's Complement
$r = substr($r, -10);
}
return self::nbrConversionFormat($r, $places);
} }
/** /**
@ -489,6 +323,8 @@ class Engineering
* Excel Function: * Excel Function:
* HEX2BIN(x[,places]) * HEX2BIN(x[,places])
* *
* @Deprecated 2.0.0 Use the toBinary() method in the Engineering\ConvertHex class instead
*
* @param string $x the hexadecimal number you want to convert. * @param string $x the hexadecimal number you want to convert.
* Number cannot contain more than 10 characters. * Number cannot contain more than 10 characters.
* The most significant bit of number is the sign bit (40th bit from the right). * The most significant bit of number is the sign bit (40th bit from the right).
@ -510,18 +346,7 @@ class Engineering
*/ */
public static function HEXTOBIN($x, $places = null) public static function HEXTOBIN($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertHex::toBinary($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
return Functions::VALUE();
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) {
return Functions::NAN();
}
return self::DECTOBIN(self::HEXTODEC($x), $places);
} }
/** /**
@ -532,6 +357,8 @@ class Engineering
* Excel Function: * Excel Function:
* HEX2DEC(x) * HEX2DEC(x)
* *
* @Deprecated 2.0.0 Use the toDecimal() method in the Engineering\ConvertHex class instead
*
* @param string $x The hexadecimal number you want to convert. This number cannot * @param string $x The hexadecimal number you want to convert. This number cannot
* contain more than 10 characters (40 bits). The most significant * contain more than 10 characters (40 bits). The most significant
* bit of number is the sign bit. The remaining 39 bits are magnitude * bit of number is the sign bit. The remaining 39 bits are magnitude
@ -544,33 +371,7 @@ class Engineering
*/ */
public static function HEXTODEC($x) public static function HEXTODEC($x)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertHex::toDecimal($x);
if (is_bool($x)) {
return Functions::VALUE();
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) {
return Functions::NAN();
}
if (strlen($x) > 10) {
return Functions::NAN();
}
$binX = '';
foreach (str_split($x) as $char) {
$binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 40 && $binX[0] == '1') {
for ($i = 0; $i < 40; ++$i) {
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (bindec($binX) + 1) * -1;
}
return bindec($binX);
} }
/** /**
@ -581,6 +382,8 @@ class Engineering
* Excel Function: * Excel Function:
* HEX2OCT(x[,places]) * HEX2OCT(x[,places])
* *
* @Deprecated 2.0.0 Use the toOctal() method in the Engineering\ConvertHex class instead
*
* @param string $x The hexadecimal number you want to convert. Number cannot * @param string $x The hexadecimal number you want to convert. Number cannot
* contain more than 10 characters. The most significant bit of * contain more than 10 characters. The most significant bit of
* number is the sign bit. The remaining 39 bits are magnitude * number is the sign bit. The remaining 39 bits are magnitude
@ -606,23 +409,7 @@ class Engineering
*/ */
public static function HEXTOOCT($x, $places = null) public static function HEXTOOCT($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertHex::toOctal($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
return Functions::VALUE();
}
$x = (string) $x;
if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) {
return Functions::NAN();
}
$decimal = self::HEXTODEC($x);
if ($decimal < -536870912 || $decimal > 536870911) {
return Functions::NAN();
}
return self::DECTOOCT($decimal, $places);
} }
/** /**
@ -633,6 +420,8 @@ class Engineering
* Excel Function: * Excel Function:
* OCT2BIN(x[,places]) * OCT2BIN(x[,places])
* *
* @Deprecated 2.0.0 Use the toBinary() method in the Engineering\ConvertOctal class instead
*
* @param string $x The octal number you want to convert. Number may not * @param string $x The octal number you want to convert. Number may not
* contain more than 10 characters. The most significant * contain more than 10 characters. The most significant
* bit of number is the sign bit. The remaining 29 bits * bit of number is the sign bit. The remaining 29 bits
@ -660,18 +449,7 @@ class Engineering
*/ */
public static function OCTTOBIN($x, $places = null) public static function OCTTOBIN($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertOctal::toBinary($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
return Functions::VALUE();
}
$x = (string) $x;
if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) {
return Functions::NAN();
}
return self::DECTOBIN(self::OCTTODEC($x), $places);
} }
/** /**
@ -682,6 +460,8 @@ class Engineering
* Excel Function: * Excel Function:
* OCT2DEC(x) * OCT2DEC(x)
* *
* @Deprecated 2.0.0 Use the toDecimal() method in the Engineering\ConvertOctal class instead
*
* @param string $x The octal number you want to convert. Number may not contain * @param string $x The octal number you want to convert. Number may not contain
* more than 10 octal characters (30 bits). The most significant * more than 10 octal characters (30 bits). The most significant
* bit of number is the sign bit. The remaining 29 bits are * bit of number is the sign bit. The remaining 29 bits are
@ -694,28 +474,7 @@ class Engineering
*/ */
public static function OCTTODEC($x) public static function OCTTODEC($x)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertOctal::toDecimal($x);
if (is_bool($x)) {
return Functions::VALUE();
}
$x = (string) $x;
if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) {
return Functions::NAN();
}
$binX = '';
foreach (str_split($x) as $char) {
$binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 30 && $binX[0] == '1') {
for ($i = 0; $i < 30; ++$i) {
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (bindec($binX) + 1) * -1;
}
return bindec($binX);
} }
/** /**
@ -726,6 +485,8 @@ class Engineering
* Excel Function: * Excel Function:
* OCT2HEX(x[,places]) * OCT2HEX(x[,places])
* *
* @Deprecated 2.0.0 Use the toHex() method in the Engineering\ConvertOctal class instead
*
* @param string $x The octal number you want to convert. Number may not contain * @param string $x The octal number you want to convert. Number may not contain
* more than 10 octal characters (30 bits). The most significant * more than 10 octal characters (30 bits). The most significant
* bit of number is the sign bit. The remaining 29 bits are * bit of number is the sign bit. The remaining 29 bits are
@ -748,19 +509,7 @@ class Engineering
*/ */
public static function OCTTOHEX($x, $places = null) public static function OCTTOHEX($x, $places = null)
{ {
$x = Functions::flattenSingleValue($x); return Engineering\ConvertOctal::toHex($x, $places);
$places = Functions::flattenSingleValue($places);
if (is_bool($x)) {
return Functions::VALUE();
}
$x = (string) $x;
if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) {
return Functions::NAN();
}
$hexVal = strtoupper(dechex((int) self::OCTTODEC((int) $x)));
return self::nbrConversionFormat($hexVal, $places);
} }
/** /**
@ -1431,7 +1180,7 @@ class Engineering
*/ */
public static function BITAND($number1, $number2) public static function BITAND($number1, $number2)
{ {
return BitWise::BITAND($number1, $number2); return Engineering\BitWise::BITAND($number1, $number2);
} }
/** /**
@ -1451,7 +1200,7 @@ class Engineering
*/ */
public static function BITOR($number1, $number2) public static function BITOR($number1, $number2)
{ {
return BitWise::BITOR($number1, $number2); return Engineering\BitWise::BITOR($number1, $number2);
} }
/** /**
@ -1471,7 +1220,7 @@ class Engineering
*/ */
public static function BITXOR($number1, $number2) public static function BITXOR($number1, $number2)
{ {
return BitWise::BITXOR($number1, $number2); return Engineering\BitWise::BITXOR($number1, $number2);
} }
/** /**
@ -1491,7 +1240,7 @@ class Engineering
*/ */
public static function BITLSHIFT($number, $shiftAmount) public static function BITLSHIFT($number, $shiftAmount)
{ {
return BitWise::BITLSHIFT($number, $shiftAmount); return Engineering\BitWise::BITLSHIFT($number, $shiftAmount);
} }
/** /**
@ -1511,7 +1260,7 @@ class Engineering
*/ */
public static function BITRSHIFT($number, $shiftAmount) public static function BITRSHIFT($number, $shiftAmount)
{ {
return BitWise::BITRSHIFT($number, $shiftAmount); return Engineering\BitWise::BITRSHIFT($number, $shiftAmount);
} }
/** /**

View File

@ -0,0 +1,63 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ConvertBase
{
protected static function validateValue($value, bool $gnumericCheck = false): string
{
if (is_bool($value)) {
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
throw new Exception(Functions::VALUE());
}
$value = (int) $value;
}
if ($gnumericCheck && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
$value = floor((float) $value);
}
return strtoupper((string) $value);
}
protected static function validatePlaces($places = null): ?int
{
if ($places === null) {
return $places;
}
if (is_numeric($places)) {
if ($places < 0) {
throw new Exception(Functions::NAN());
}
return (int) $places;
}
throw new Exception(Functions::VALUE());
}
/**
* Formats a number base string value with leading zeroes.
*
* @param string $value The "number" to pad
* @param ?int $places The length that we want to pad this value
*
* @return string The padded "number"
*/
protected static function nbrConversionFormat(string $value, ?int $places): string
{
if ($places !== null) {
if (strlen($value) <= $places) {
return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10);
}
return Functions::NAN();
}
return substr($value, -10);
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ConvertBinary extends ConvertBase
{
/**
* toDecimal.
*
* Return a binary value as decimal.
*
* Excel Function:
* BIN2DEC(x)
*
* @param string $value The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2DEC returns the #NUM! error value.
*/
public static function toDecimal($value): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value), true);
$value = self::validateBinary($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10) {
// Two's Complement
$value = substr($value, -9);
return '-' . (512 - bindec($value));
}
return bindec($value);
}
/**
* toHex.
*
* Return a binary value as hex.
*
* Excel Function:
* BIN2HEX(x[,places])
*
* @param string $value The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2HEX returns the #NUM! error value.
* @param int $places The number of characters to use. If places is omitted, BIN2HEX uses the
* minimum number of characters necessary. Places is useful for padding the
* return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, BIN2HEX returns the #VALUE! error value.
* If places is negative, BIN2HEX returns the #NUM! error value.
*/
public static function toHex($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value), true);
$value = self::validateBinary($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10) {
// Two's Complement
return str_repeat('F', 8) . substr(strtoupper(dechex((int) bindec(substr($value, -9)))), -2);
}
$hexVal = (string) strtoupper(dechex((int) bindec($value)));
return self::nbrConversionFormat($hexVal, $places);
}
/**
* toOctal.
*
* Return a binary value as octal.
*
* Excel Function:
* BIN2OCT(x[,places])
*
* @param string $value The binary number (as a string) that you want to convert. The number
* cannot contain more than 10 characters (10 bits). The most significant
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2OCT returns the #NUM! error value.
* @param int $places The number of characters to use. If places is omitted, BIN2OCT uses the
* minimum number of characters necessary. Places is useful for padding the
* return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, BIN2OCT returns the #VALUE! error value.
* If places is negative, BIN2OCT returns the #NUM! error value.
*/
public static function toOctal($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value), true);
$value = self::validateBinary($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10) {
// Two's Complement
return str_repeat('7', 7) . substr(strtoupper(decoct((int) bindec(substr($value, -9)))), -3);
}
$octVal = (string) decoct((int) bindec($value));
return self::nbrConversionFormat($octVal, $places);
}
protected static function validateBinary(string $value): string
{
if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) {
throw new Exception(Functions::NAN());
}
return $value;
}
}

View File

@ -0,0 +1,158 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ConvertDecimal extends ConvertBase
{
/**
* toBinary.
*
* Return a decimal value as binary.
*
* Excel Function:
* DEC2BIN(x[,places])
*
* @param string $value The decimal integer you want to convert. If number is negative,
* valid place values are ignored and DEC2BIN returns a 10-character
* (10-bit) binary number in which the most significant bit is the sign
* bit. The remaining 9 bits are magnitude bits. Negative numbers are
* represented using two's-complement notation.
* If number < -512 or if number > 511, DEC2BIN returns the #NUM! error
* value.
* If number is nonnumeric, DEC2BIN returns the #VALUE! error value.
* If DEC2BIN requires more than places characters, it returns the #NUM!
* error value.
* @param int $places The number of characters to use. If places is omitted, DEC2BIN uses
* the minimum number of characters necessary. Places is useful for
* padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, DEC2BIN returns the #VALUE! error value.
* If places is zero or negative, DEC2BIN returns the #NUM! error value.
*/
public static function toBinary($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateDecimal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
if ($value < -512 || $value > 511) {
return Functions::NAN();
}
$r = decbin($value);
// Two's Complement
$r = substr($r, -10);
if (strlen($r) >= 11) {
return Functions::NAN();
}
return self::nbrConversionFormat($r, $places);
}
/**
* toHex.
*
* Return a decimal value as hex.
*
* Excel Function:
* DEC2HEX(x[,places])
*
* @param string $value The decimal integer you want to convert. If number is negative,
* places is ignored and DEC2HEX returns a 10-character (40-bit)
* hexadecimal number in which the most significant bit is the sign
* bit. The remaining 39 bits are magnitude bits. Negative numbers
* are represented using two's-complement notation.
* If number < -549,755,813,888 or if number > 549,755,813,887,
* DEC2HEX returns the #NUM! error value.
* If number is nonnumeric, DEC2HEX returns the #VALUE! error value.
* If DEC2HEX requires more than places characters, it returns the
* #NUM! error value.
* @param int $places The number of characters to use. If places is omitted, DEC2HEX uses
* the minimum number of characters necessary. Places is useful for
* padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, DEC2HEX returns the #VALUE! error value.
* If places is zero or negative, DEC2HEX returns the #NUM! error value.
*/
public static function toHex($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateDecimal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
$r = strtoupper(dechex($value));
if (strlen($r) == 8) {
// Two's Complement
$r = 'FF' . $r;
}
return self::nbrConversionFormat($r, $places);
}
/**
* toOctal.
*
* Return an decimal value as octal.
*
* Excel Function:
* DEC2OCT(x[,places])
*
* @param string $value The decimal integer you want to convert. If number is negative,
* places is ignored and DEC2OCT returns a 10-character (30-bit)
* octal number in which the most significant bit is the sign bit.
* The remaining 29 bits are magnitude bits. Negative numbers are
* represented using two's-complement notation.
* If number < -536,870,912 or if number > 536,870,911, DEC2OCT
* returns the #NUM! error value.
* If number is nonnumeric, DEC2OCT returns the #VALUE! error value.
* If DEC2OCT requires more than places characters, it returns the
* #NUM! error value.
* @param int $places The number of characters to use. If places is omitted, DEC2OCT uses
* the minimum number of characters necessary. Places is useful for
* padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, DEC2OCT returns the #VALUE! error value.
* If places is zero or negative, DEC2OCT returns the #NUM! error value.
*/
public static function toOctal($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateDecimal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
$r = decoct($value);
if (strlen($r) == 11) {
// Two's Complement
$r = substr($r, -10);
}
return self::nbrConversionFormat($r, $places);
}
protected static function validateDecimal(string $value): string
{
if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) {
throw new Exception(Functions::VALUE());
}
return $value;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ConvertHex extends ConvertBase
{
/**
* toBinary.
*
* Return a hex value as binary.
*
* Excel Function:
* HEX2BIN(x[,places])
*
* @param string $value The hexadecimal number you want to convert.
* Number cannot contain more than 10 characters.
* The most significant bit of number is the sign bit (40th bit from the right).
* The remaining 9 bits are magnitude bits.
* Negative numbers are represented using two's-complement notation.
* If number is negative, HEX2BIN ignores places and returns a 10-character binary number.
* If number is negative, it cannot be less than FFFFFFFE00,
* and if number is positive, it cannot be greater than 1FF.
* If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value.
* If HEX2BIN requires more than places characters, it returns the #NUM! error value.
* @param int $places The number of characters to use. If places is omitted,
* HEX2BIN uses the minimum number of characters necessary. Places
* is useful for padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, HEX2BIN returns the #VALUE! error value.
* If places is negative, HEX2BIN returns the #NUM! error value.
*/
public static function toBinary($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateHex($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
return ConvertDecimal::toBinary(self::toDecimal($value), $places);
}
/**
* toDecimal.
*
* Return a hex value as decimal.
*
* Excel Function:
* HEX2DEC(x)
*
* @param string $value The hexadecimal number you want to convert. This number cannot
* contain more than 10 characters (40 bits). The most significant
* bit of number is the sign bit. The remaining 39 bits are magnitude
* bits. Negative numbers are represented using two's-complement
* notation.
* If number is not a valid hexadecimal number, HEX2DEC returns the
* #NUM! error value.
*/
public static function toDecimal($value): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateHex($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) > 10) {
return Functions::NAN();
}
$binX = '';
foreach (str_split($value) as $char) {
$binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 40 && $binX[0] == '1') {
for ($i = 0; $i < 40; ++$i) {
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (bindec($binX) + 1) * -1;
}
return bindec($binX);
}
/**
* toOctal.
*
* Return a hex value as octal.
*
* Excel Function:
* HEX2OCT(x[,places])
*
* @param string $value The hexadecimal number you want to convert. Number cannot
* contain more than 10 characters. The most significant bit of
* number is the sign bit. The remaining 39 bits are magnitude
* bits. Negative numbers are represented using two's-complement
* notation.
* If number is negative, HEX2OCT ignores places and returns a
* 10-character octal number.
* If number is negative, it cannot be less than FFE0000000, and
* if number is positive, it cannot be greater than 1FFFFFFF.
* If number is not a valid hexadecimal number, HEX2OCT returns
* the #NUM! error value.
* If HEX2OCT requires more than places characters, it returns
* the #NUM! error value.
* @param int $places The number of characters to use. If places is omitted, HEX2OCT
* uses the minimum number of characters necessary. Places is
* useful for padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, HEX2OCT returns the #VALUE! error
* value.
* If places is negative, HEX2OCT returns the #NUM! error value.
*/
public static function toOctal($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateHex($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
$decimal = self::toDecimal($value);
if ($decimal < -536870912 || $decimal > 536870911) {
return Functions::NAN();
}
return ConvertDecimal::toOctal($decimal, $places);
}
protected static function validateHex(string $value): string
{
if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) {
throw new Exception(Functions::NAN());
}
return $value;
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ConvertOctal extends ConvertBase
{
/**
* toBinary.
*
* Return an octal value as binary.
*
* Excel Function:
* OCT2BIN(x[,places])
*
* @param string $value The octal number you want to convert. Number may not
* contain more than 10 characters. The most significant
* bit of number is the sign bit. The remaining 29 bits
* are magnitude bits. Negative numbers are represented
* using two's-complement notation.
* If number is negative, OCT2BIN ignores places and returns
* a 10-character binary number.
* If number is negative, it cannot be less than 7777777000,
* and if number is positive, it cannot be greater than 777.
* If number is not a valid octal number, OCT2BIN returns
* the #NUM! error value.
* If OCT2BIN requires more than places characters, it
* returns the #NUM! error value.
* @param int $places The number of characters to use. If places is omitted,
* OCT2BIN uses the minimum number of characters necessary.
* Places is useful for padding the return value with
* leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, OCT2BIN returns the #VALUE!
* error value.
* If places is negative, OCT2BIN returns the #NUM! error
* value.
*/
public static function toBinary($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateOctal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
return ConvertDecimal::toBinary(self::toDecimal($value), $places);
}
/**
* toDecimal.
*
* Return an octal value as decimal.
*
* Excel Function:
* OCT2DEC(x)
*
* @param string $value The octal number you want to convert. Number may not contain
* more than 10 octal characters (30 bits). The most significant
* bit of number is the sign bit. The remaining 29 bits are
* magnitude bits. Negative numbers are represented using
* two's-complement notation.
* If number is not a valid octal number, OCT2DEC returns the
* #NUM! error value.
*/
public static function toDecimal($value): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateOctal($value);
} catch (Exception $e) {
return $e->getMessage();
}
$binX = '';
foreach (str_split($value) as $char) {
$binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 30 && $binX[0] == '1') {
for ($i = 0; $i < 30; ++$i) {
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (bindec($binX) + 1) * -1;
}
return bindec($binX);
}
/**
* toHex.
*
* Return an octal value as hex.
*
* Excel Function:
* OCT2HEX(x[,places])
*
* @param string $value The octal number you want to convert. Number may not contain
* more than 10 octal characters (30 bits). The most significant
* bit of number is the sign bit. The remaining 29 bits are
* magnitude bits. Negative numbers are represented using
* two's-complement notation.
* If number is negative, OCT2HEX ignores places and returns a
* 10-character hexadecimal number.
* If number is not a valid octal number, OCT2HEX returns the
* #NUM! error value.
* If OCT2HEX requires more than places characters, it returns
* the #NUM! error value.
* @param int $places The number of characters to use. If places is omitted, OCT2HEX
* uses the minimum number of characters necessary. Places is useful
* for padding the return value with leading 0s (zeros).
* If places is not an integer, it is truncated.
* If places is nonnumeric, OCT2HEX returns the #VALUE! error value.
* If places is negative, OCT2HEX returns the #NUM! error value.
*/
public static function toHex($value, $places = null): string
{
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateOctal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
} catch (Exception $e) {
return $e->getMessage();
}
$hexVal = strtoupper(dechex((int) self::toDecimal((int) $value)));
return self::nbrConversionFormat($hexVal, $places);
}
protected static function validateOctal(string $value): string
{
if (strlen($value) > preg_match_all('/[01234567]/', $value)) {
throw new Exception(Functions::NAN());
}
return $value;
}
}