diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 1bb73350..4c7d7b79 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -403,17 +403,17 @@ class Calculation ], 'BIN2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTODEC'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'], 'argumentCount' => '1', ], 'BIN2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTOHEX'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'], 'argumentCount' => '1,2', ], 'BIN2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTOOCT'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'], 'argumentCount' => '1,2', ], 'BINOMDIST' => [ @@ -814,17 +814,17 @@ class Calculation ], 'DEC2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOBIN'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'DEC2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOHEX'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'], 'argumentCount' => '1,2', ], 'DEC2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOOCT'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'], 'argumentCount' => '1,2', ], 'DECIMAL' => [ @@ -1216,17 +1216,17 @@ class Calculation ], 'HEX2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTOBIN'], + 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'], 'argumentCount' => '1,2', ], 'HEX2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTODEC'], + 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'], 'argumentCount' => '1', ], 'HEX2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTOOCT'], + 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'], 'argumentCount' => '1,2', ], 'HLOOKUP' => [ @@ -1840,17 +1840,17 @@ class Calculation ], 'OCT2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTOBIN'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'OCT2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTODEC'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'], 'argumentCount' => '1', ], 'OCT2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTOHEX'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'], 'argumentCount' => '1,2', ], 'ODD' => [ diff --git a/src/PhpSpreadsheet/Calculation/Engineering.php b/src/PhpSpreadsheet/Calculation/Engineering.php index 3bf04238..1429c7a9 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering.php +++ b/src/PhpSpreadsheet/Calculation/Engineering.php @@ -4,9 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use Complex\Complex; 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 { @@ -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. * @@ -169,6 +137,8 @@ class Engineering * Excel Function: * 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 * 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. @@ -180,32 +150,7 @@ class Engineering */ public static function BINTODEC($x) { - $x = Functions::flattenSingleValue($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); + return Engineering\ConvertBinary::toDecimal($x); } /** @@ -216,6 +161,8 @@ class Engineering * Excel Function: * 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 * 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. @@ -233,33 +180,7 @@ class Engineering */ public static function BINTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $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); + return Engineering\ConvertBinary::toHex($x, $places); } /** @@ -270,6 +191,8 @@ class Engineering * Excel Function: * 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 * 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. @@ -287,32 +210,7 @@ class Engineering */ public static function BINTOOCT($x, $places = null) { - $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(); - } - } - 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); + return Engineering\ConvertBinary::toOctal($x, $places); } /** @@ -323,6 +221,8 @@ class Engineering * Excel Function: * 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, * valid place values are ignored and DEC2BIN returns a 10-character * (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) { - $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); - 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); + return Engineering\ConvertDecimal::toBinary($x, $places); } /** @@ -382,6 +255,8 @@ class Engineering * Excel Function: * 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, * places is ignored and DEC2HEX returns a 10-character (40-bit) * hexadecimal number in which the most significant bit is the sign @@ -403,28 +278,7 @@ class Engineering */ public static function DECTOHEX($x, $places = null) { - $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 = strtoupper(dechex($x)); - if (strlen($r) == 8) { - // Two's Complement - $r = 'FF' . $r; - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toHex($x, $places); } /** @@ -435,6 +289,8 @@ class Engineering * Excel Function: * 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, * places is ignored and DEC2OCT returns a 10-character (30-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) { - $xorig = $x; - $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); + return Engineering\ConvertDecimal::toOctal($x, $places); } /** @@ -489,6 +323,8 @@ class Engineering * Excel Function: * 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. * Number cannot contain more than 10 characters. * 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) { - $x = Functions::flattenSingleValue($x); - $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); + return Engineering\ConvertHex::toBinary($x, $places); } /** @@ -532,6 +357,8 @@ class Engineering * Excel Function: * 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 * contain more than 10 characters (40 bits). The most significant * bit of number is the sign bit. The remaining 39 bits are magnitude @@ -544,33 +371,7 @@ class Engineering */ public static function HEXTODEC($x) { - $x = Functions::flattenSingleValue($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); + return Engineering\ConvertHex::toDecimal($x); } /** @@ -581,6 +382,8 @@ class Engineering * Excel Function: * 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 * contain more than 10 characters. The most significant bit of * number is the sign bit. The remaining 39 bits are magnitude @@ -606,23 +409,7 @@ class Engineering */ public static function HEXTOOCT($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $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); + return Engineering\ConvertHex::toOctal($x, $places); } /** @@ -633,6 +420,8 @@ class Engineering * Excel Function: * 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 * contain more than 10 characters. The most significant * bit of number is the sign bit. The remaining 29 bits @@ -660,18 +449,7 @@ class Engineering */ public static function OCTTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $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); + return Engineering\ConvertOctal::toBinary($x, $places); } /** @@ -682,6 +460,8 @@ class Engineering * Excel Function: * 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 * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are @@ -694,28 +474,7 @@ class Engineering */ public static function OCTTODEC($x) { - $x = Functions::flattenSingleValue($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); + return Engineering\ConvertOctal::toDecimal($x); } /** @@ -726,6 +485,8 @@ class Engineering * Excel Function: * 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 * more than 10 octal characters (30 bits). The most significant * 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) { - $x = Functions::flattenSingleValue($x); - $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); + return Engineering\ConvertOctal::toHex($x, $places); } /** @@ -1431,7 +1180,7 @@ class Engineering */ 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) { - return BitWise::BITOR($number1, $number2); + return Engineering\BitWise::BITOR($number1, $number2); } /** @@ -1471,7 +1220,7 @@ class Engineering */ 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) { - return BitWise::BITLSHIFT($number, $shiftAmount); + return Engineering\BitWise::BITLSHIFT($number, $shiftAmount); } /** @@ -1511,7 +1260,7 @@ class Engineering */ public static function BITRSHIFT($number, $shiftAmount) { - return BitWise::BITRSHIFT($number, $shiftAmount); + return Engineering\BitWise::BITRSHIFT($number, $shiftAmount); } /** diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php new file mode 100644 index 00000000..5122e011 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php @@ -0,0 +1,63 @@ +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; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php new file mode 100644 index 00000000..bd249633 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php @@ -0,0 +1,158 @@ + 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; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php new file mode 100644 index 00000000..9147bf08 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php @@ -0,0 +1,147 @@ +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; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php new file mode 100644 index 00000000..1e8823ad --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php @@ -0,0 +1,143 @@ +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; + } +}