This commit is contained in:
steven 2025-08-11 22:23:30 +02:00
commit 72a26edcff
22092 changed files with 2101903 additions and 0 deletions

View file

@ -0,0 +1,141 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselI
{
use ArrayEnabled;
/**
* BESSELI.
*
* Returns the modified Bessel function In(x), which is equivalent to the Bessel function evaluated
* for purely imaginary arguments
*
* Excel Function:
* BESSELI(x,ord)
*
* NOTE: The MS Excel implementation of the BESSELI function is still not accurate.
* This code provides a more accurate calculation
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELI returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
* If $ord < 0, BESSELI returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELI(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if ($ord < 0) {
return ExcelError::NAN();
}
$fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? ExcelError::NAN() : $fResult;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselI0($x),
1 => self::besselI1($x),
default => self::besselI2($x, $ord),
};
}
private static function besselI0(float $x): float
{
$ax = abs($x);
if ($ax < 3.75) {
$y = $x / 3.75;
$y = $y * $y;
return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492
+ $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2)))));
}
$y = 3.75 / $ax;
return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2
+ $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1
+ $y * (-0.1647633e-1 + $y * 0.392377e-2))))))));
}
private static function besselI1(float $x): float
{
$ax = abs($x);
if ($ax < 3.75) {
$y = $x / 3.75;
$y = $y * $y;
$ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1
+ $y * (0.301532e-2 + $y * 0.32411e-3))))));
return ($x < 0.0) ? -$ans : $ans;
}
$y = 3.75 / $ax;
$ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2));
$ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2
+ $y * (-0.1031555e-1 + $y * $ans))));
$ans *= exp($ax) / sqrt($ax);
return ($x < 0.0) ? -$ans : $ans;
}
private static function besselI2(float $x, int $ord): float
{
if ($x === 0.0) {
return 0.0;
}
$tox = 2.0 / abs($x);
$bip = 0;
$ans = 0.0;
$bi = 1.0;
for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
$bim = $bip + $j * $tox * $bi;
$bip = $bi;
$bi = $bim;
if (abs($bi) > 1.0e+12) {
$ans *= 1.0e-12;
$bi *= 1.0e-12;
$bip *= 1.0e-12;
}
if ($j === $ord) {
$ans = $bip;
}
}
$ans *= self::besselI0($x) / $bi;
return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans;
}
}

View file

@ -0,0 +1,176 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselJ
{
use ArrayEnabled;
/**
* BESSELJ.
*
* Returns the Bessel function
*
* Excel Function:
* BESSELJ(x,ord)
*
* NOTE: The MS Excel implementation of the BESSELJ function is still not accurate, particularly for higher order
* values with x < -8 and x > 8. This code provides a more accurate calculation
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELJ returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELJ returns the #VALUE! error value.
* If $ord < 0, BESSELJ returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELJ(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if ($ord < 0) {
return ExcelError::NAN();
}
$fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? ExcelError::NAN() : $fResult;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselJ0($x),
1 => self::besselJ1($x),
default => self::besselJ2($x, $ord),
};
}
private static function besselJ0(float $x): float
{
$ax = abs($x);
if ($ax < 8.0) {
$y = $x * $x;
$ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y
* (77392.33017 + $y * (-184.9052456)))));
$ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y
* (267.8532712 + $y * 1.0))));
return $ans1 / $ans2;
}
$z = 8.0 / $ax;
$y = $z * $z;
$xx = $ax - 0.785398164;
$ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
$ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y
* (0.7621095161e-6 - $y * 0.934935152e-7)));
return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
}
private static function besselJ1(float $x): float
{
$ax = abs($x);
if ($ax < 8.0) {
$y = $x * $x;
$ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y
* (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606))))));
$ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y
* (376.9991397 + $y * 1.0))));
return $ans1 / $ans2;
}
$z = 8.0 / $ax;
$y = $z * $z;
$xx = $ax - 2.356194491;
$ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
$ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y
* (-0.88228987e-6 + $y * 0.105787412e-6)));
$ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
return ($x < 0.0) ? -$ans : $ans;
}
private static function besselJ2(float $x, int $ord): float
{
$ax = abs($x);
if ($ax === 0.0) {
return 0.0;
}
if ($ax > $ord) {
return self::besselj2a($ax, $ord, $x);
}
return self::besselj2b($ax, $ord, $x);
}
private static function besselj2a(float $ax, int $ord, float $x): float
{
$tox = 2.0 / $ax;
$bjm = self::besselJ0($ax);
$bj = self::besselJ1($ax);
for ($j = 1; $j < $ord; ++$j) {
$bjp = $j * $tox * $bj - $bjm;
$bjm = $bj;
$bj = $bjp;
}
$ans = $bj;
return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans;
}
private static function besselj2b(float $ax, int $ord, float $x): float
{
$tox = 2.0 / $ax;
$jsum = false;
$bjp = $ans = $sum = 0.0;
$bj = 1.0;
for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
$bjm = $j * $tox * $bj - $bjp;
$bjp = $bj;
$bj = $bjm;
if (abs($bj) > 1.0e+10) {
$bj *= 1.0e-10;
$bjp *= 1.0e-10;
$ans *= 1.0e-10;
$sum *= 1.0e-10;
}
if ($jsum === true) {
$sum += $bj;
}
$jsum = $jsum === false;
if ($j === $ord) {
$ans = $bjp;
}
}
$sum = 2.0 * $sum - $bj;
$ans /= $sum;
return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselK
{
use ArrayEnabled;
/**
* BESSELK.
*
* Returns the modified Bessel function Kn(x), which is equivalent to the Bessel functions evaluated
* for purely imaginary arguments.
*
* Excel Function:
* BESSELK(x,ord)
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELK returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
* If $ord < 0, BESSELKI returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELK(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if (($ord < 0) || ($x <= 0.0)) {
return ExcelError::NAN();
}
$fBk = self::calculate($x, $ord);
return (is_nan($fBk)) ? ExcelError::NAN() : $fBk;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselK0($x),
1 => self::besselK1($x),
default => self::besselK2($x, $ord),
};
}
/**
* Mollify Phpstan.
*
* @codeCoverageIgnore
*/
private static function callBesselI(float $x, int $ord): float
{
$rslt = BesselI::BESSELI($x, $ord);
if (!is_float($rslt)) {
throw new Exception('Unexpected array or string');
}
return $rslt;
}
private static function besselK0(float $x): float
{
if ($x <= 2) {
$fNum2 = $x * 0.5;
$y = ($fNum2 * $fNum2);
return -log($fNum2) * self::callBesselI($x, 0)
+ (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y
* (0.10750e-3 + $y * 0.74e-5))))));
}
$y = 2 / $x;
return exp(-$x) / sqrt($x)
* (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y
* (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
}
private static function besselK1(float $x): float
{
if ($x <= 2) {
$fNum2 = $x * 0.5;
$y = ($fNum2 * $fNum2);
return log($fNum2) * self::callBesselI($x, 1)
+ (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y
* (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x;
}
$y = 2 / $x;
return exp(-$x) / sqrt($x)
* (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y
* (0.325614e-2 + $y * (-0.68245e-3)))))));
}
private static function besselK2(float $x, int $ord): float
{
$fTox = 2 / $x;
$fBkm = self::besselK0($x);
$fBk = self::besselK1($x);
for ($n = 1; $n < $ord; ++$n) {
$fBkp = $fBkm + $n * $fTox * $fBk;
$fBkm = $fBk;
$fBk = $fBkp;
}
return $fBk;
}
}

View file

@ -0,0 +1,137 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselY
{
use ArrayEnabled;
/**
* BESSELY.
*
* Returns the Bessel function, which is also called the Weber function or the Neumann function.
*
* Excel Function:
* BESSELY(x,ord)
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELY returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
* If $ord < 0, BESSELY returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELY(mixed $x, mixed $ord): array|string|float
{
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
$ord = EngineeringValidations::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if (($ord < 0) || ($x <= 0.0)) {
return ExcelError::NAN();
}
$fBy = self::calculate($x, $ord);
return (is_nan($fBy)) ? ExcelError::NAN() : $fBy;
}
private static function calculate(float $x, int $ord): float
{
return match ($ord) {
0 => self::besselY0($x),
1 => self::besselY1($x),
default => self::besselY2($x, $ord),
};
}
/**
* Mollify Phpstan.
*
* @codeCoverageIgnore
*/
private static function callBesselJ(float $x, int $ord): float
{
$rslt = BesselJ::BESSELJ($x, $ord);
if (!is_float($rslt)) {
throw new Exception('Unexpected array or string');
}
return $rslt;
}
private static function besselY0(float $x): float
{
if ($x < 8.0) {
$y = ($x * $x);
$ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y
* (-86327.92757 + $y * 228.4622733))));
$ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y
* (47447.26470 + $y * (226.1030244 + $y))));
return $ans1 / $ans2 + 0.636619772 * self::callBesselJ($x, 0) * log($x);
}
$z = 8.0 / $x;
$y = ($z * $z);
$xx = $x - 0.785398164;
$ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
$ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y
* (-0.934945152e-7))));
return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
}
private static function besselY1(float $x): float
{
if ($x < 8.0) {
$y = ($x * $x);
$ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y
* (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4)))));
$ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y
* (0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
return ($ans1 / $ans2) + 0.636619772 * (self::callBesselJ($x, 1) * log($x) - 1 / $x);
}
$z = 8.0 / $x;
$y = $z * $z;
$xx = $x - 2.356194491;
$ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
$ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y
* (-0.88228987e-6 + $y * 0.105787412e-6)));
return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
}
private static function besselY2(float $x, int $ord): float
{
$fTox = 2.0 / $x;
$fBym = self::besselY0($x);
$fBy = self::besselY1($x);
for ($n = 1; $n < $ord; ++$n) {
$fByp = $n * $fTox * $fBy - $fBym;
$fBym = $fBy;
$fBy = $fByp;
}
return $fBy;
}
}

View file

@ -0,0 +1,247 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BitWise
{
use ArrayEnabled;
const SPLIT_DIVISOR = 2 ** 24;
/**
* Split a number into upper and lower portions for full 32-bit support.
*
* @return int[]
*/
private static function splitNumber(float|int $number): array
{
return [(int) floor($number / self::SPLIT_DIVISOR), (int) fmod($number, self::SPLIT_DIVISOR)];
}
/**
* BITAND.
*
* Returns the bitwise AND of two integer values.
*
* Excel Function:
* BITAND(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITAND(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
} catch (Exception $e) {
return $e->getMessage();
}
$split1 = self::splitNumber($number1);
$split2 = self::splitNumber($number2);
return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]);
}
/**
* BITOR.
*
* Returns the bitwise OR of two integer values.
*
* Excel Function:
* BITOR(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITOR(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
} catch (Exception $e) {
return $e->getMessage();
}
$split1 = self::splitNumber($number1);
$split2 = self::splitNumber($number2);
return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]);
}
/**
* BITXOR.
*
* Returns the bitwise XOR of two integer values.
*
* Excel Function:
* BITXOR(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITXOR(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
} catch (Exception $e) {
return $e->getMessage();
}
$split1 = self::splitNumber($number1);
$split2 = self::splitNumber($number2);
return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]);
}
/**
* BITLSHIFT.
*
* Returns the number value shifted left by shift_amount bits.
*
* Excel Function:
* BITLSHIFT(number, shift_amount)
*
* @param null|array|bool|float|int|string $number Or can be an array of values
* @param null|array|bool|float|int|string $shiftAmount Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITLSHIFT(null|array|bool|float|int|string $number, null|array|bool|float|int|string $shiftAmount): array|string|float
{
if (is_array($number) || is_array($shiftAmount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
}
try {
$number = self::validateBitwiseArgument($number);
$shiftAmount = self::validateShiftAmount($shiftAmount);
} catch (Exception $e) {
return $e->getMessage();
}
$result = floor($number * (2 ** $shiftAmount));
if ($result > 2 ** 48 - 1) {
return ExcelError::NAN();
}
return $result;
}
/**
* BITRSHIFT.
*
* Returns the number value shifted right by shift_amount bits.
*
* Excel Function:
* BITRSHIFT(number, shift_amount)
*
* @param null|array|bool|float|int|string $number Or can be an array of values
* @param null|array|bool|float|int|string $shiftAmount Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITRSHIFT(null|array|bool|float|int|string $number, null|array|bool|float|int|string $shiftAmount): array|string|float
{
if (is_array($number) || is_array($shiftAmount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
}
try {
$number = self::validateBitwiseArgument($number);
$shiftAmount = self::validateShiftAmount($shiftAmount);
} catch (Exception $e) {
return $e->getMessage();
}
$result = floor($number / (2 ** $shiftAmount));
if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative
return ExcelError::NAN();
}
return $result;
}
/**
* Validate arguments passed to the bitwise functions.
*/
private static function validateBitwiseArgument(mixed $value): float
{
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
$value = (float) $value;
if ($value == floor($value)) {
if (($value > 2 ** 48 - 1) || ($value < 0)) {
throw new Exception(ExcelError::NAN());
}
return floor($value);
}
throw new Exception(ExcelError::NAN());
}
throw new Exception(ExcelError::VALUE());
}
/**
* Validate arguments passed to the bitwise functions.
*/
private static function validateShiftAmount(mixed $value): int
{
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
if (abs($value) > 53) {
throw new Exception(ExcelError::NAN());
}
return (int) $value;
}
throw new Exception(ExcelError::VALUE());
}
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*/
private static function nullFalseTrueToNumber(mixed &$number): mixed
{
if ($number === null) {
$number = 0;
} elseif (is_bool($number)) {
$number = (int) $number;
}
return $number;
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Compare
{
use ArrayEnabled;
/**
* DELTA.
*
* Excel Function:
* DELTA(a[,b])
*
* Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
* Use this function to filter a set of values. For example, by summing several DELTA
* functions you calculate the count of equal pairs. This function is also known as the
* Kronecker Delta function.
*
* @param array|bool|float|int|string $a the first number
* Or can be an array of values
* @param array|bool|float|int|string $b The second number. If omitted, b is assumed to be zero.
* Or can be an array of values
*
* @return array|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function DELTA(array|float|bool|string|int $a, array|float|bool|string|int $b = 0.0): array|string|int
{
if (is_array($a) || is_array($b)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $a, $b);
}
try {
$a = EngineeringValidations::validateFloat($a);
$b = EngineeringValidations::validateFloat($b);
} catch (Exception $e) {
return $e->getMessage();
}
return (int) (abs($a - $b) < 1.0e-15);
}
/**
* GESTEP.
*
* Excel Function:
* GESTEP(number[,step])
*
* Returns 1 if number >= step; returns 0 (zero) otherwise
* Use this function to filter a set of values. For example, by summing several GESTEP
* functions you calculate the count of values that exceed a threshold.
*
* @param array|bool|float|int|string $number the value to test against step
* Or can be an array of values
* @param null|array|bool|float|int|string $step The threshold value. If you omit a value for step, GESTEP uses zero.
* Or can be an array of values
*
* @return array|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function GESTEP(array|float|bool|string|int $number, $step = 0.0): array|string|int
{
if (is_array($number) || is_array($step)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $step);
}
try {
$number = EngineeringValidations::validateFloat($number);
$step = EngineeringValidations::validateFloat($step ?? 0.0);
} catch (Exception $e) {
return $e->getMessage();
}
return (int) ($number >= $step);
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Complex
{
use ArrayEnabled;
/**
* COMPLEX.
*
* Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj.
*
* Excel Function:
* COMPLEX(realNumber,imaginary[,suffix])
*
* @param mixed $realNumber the real float coefficient of the complex number
* Or can be an array of values
* @param mixed $imaginary the imaginary float coefficient of the complex number
* Or can be an array of values
* @param mixed $suffix The character suffix for the imaginary component of the complex number.
* If omitted, the suffix is assumed to be "i".
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function COMPLEX(mixed $realNumber = 0.0, mixed $imaginary = 0.0, mixed $suffix = 'i'): array|string
{
if (is_array($realNumber) || is_array($imaginary) || is_array($suffix)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $realNumber, $imaginary, $suffix);
}
$realNumber = $realNumber ?? 0.0;
$imaginary = $imaginary ?? 0.0;
$suffix = $suffix ?? 'i';
try {
$realNumber = EngineeringValidations::validateFloat($realNumber);
$imaginary = EngineeringValidations::validateFloat($imaginary);
} catch (Exception $e) {
return $e->getMessage();
}
if (($suffix === 'i') || ($suffix === 'j') || ($suffix === '')) {
$complex = new ComplexObject($realNumber, $imaginary, $suffix);
return (string) $complex;
}
return ExcelError::VALUE();
}
/**
* IMAGINARY.
*
* Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMAGINARY(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the imaginary
* coefficient
* Or can be an array of values
*
* @return array|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMAGINARY($complexNumber): array|string|float
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return $complex->getImaginary();
}
/**
* IMREAL.
*
* Returns the real coefficient of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMREAL(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the real coefficient
* Or can be an array of values
*
* @return array|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMREAL($complexNumber): array|string|float
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return $complex->getReal();
}
}

View file

@ -0,0 +1,592 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ComplexFunctions
{
use ArrayEnabled;
/**
* IMABS.
*
* Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMABS(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the absolute value
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMABS(array|string $complexNumber): array|float|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return $complex->abs();
}
/**
* IMARGUMENT.
*
* Returns the argument theta of a complex number, i.e. the angle in radians from the real
* axis to the representation of the number in polar coordinates.
*
* Excel Function:
* IMARGUMENT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the argument theta
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMARGUMENT(array|string $complexNumber): array|float|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::DIV0();
}
return $complex->argument();
}
/**
* IMCONJUGATE.
*
* Returns the complex conjugate of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCONJUGATE(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the conjugate
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCONJUGATE(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->conjugate();
}
/**
* IMCOS.
*
* Returns the cosine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCOS(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cosine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOS(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->cos();
}
/**
* IMCOSH.
*
* Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCOSH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOSH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->cosh();
}
/**
* IMCOT.
*
* Returns the cotangent of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCOT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cotangent
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOT(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->cot();
}
/**
* IMCSC.
*
* Returns the cosecant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCSC(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cosecant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSC(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->csc();
}
/**
* IMCSCH.
*
* Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMCSCH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSCH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->csch();
}
/**
* IMSIN.
*
* Returns the sine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSIN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the sine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSIN(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sin();
}
/**
* IMSINH.
*
* Returns the hyperbolic sine of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSINH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic sine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSINH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sinh();
}
/**
* IMSEC.
*
* Returns the secant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSEC(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the secant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSEC(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sec();
}
/**
* IMSECH.
*
* Returns the hyperbolic secant of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSECH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic secant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSECH(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->sech();
}
/**
* IMTAN.
*
* Returns the tangent of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMTAN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the tangent
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMTAN(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->tan();
}
/**
* IMSQRT.
*
* Returns the square root of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMSQRT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the square root
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSQRT(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
$theta = self::IMARGUMENT($complexNumber);
if ($theta === ExcelError::DIV0()) {
return '0';
}
return (string) $complex->sqrt();
}
/**
* IMLN.
*
* Returns the natural logarithm of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMLN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the natural logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLN(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::NAN();
}
return (string) $complex->ln();
}
/**
* IMLOG10.
*
* Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMLOG10(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the common logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG10(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::NAN();
}
return (string) $complex->log10();
}
/**
* IMLOG2.
*
* Returns the base-2 logarithm of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMLOG2(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the base-2 logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG2(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return ExcelError::NAN();
}
return (string) $complex->log2();
}
/**
* IMEXP.
*
* Returns the exponential of a complex number in x + yi or x + yj text format.
*
* Excel Function:
* IMEXP(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the exponential
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMEXP(array|string $complexNumber): array|string
{
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $complex->exp();
}
/**
* IMPOWER.
*
* Returns a complex number in x + yi or x + yj text format raised to a power.
*
* Excel Function:
* IMPOWER(complexNumber,realNumber)
*
* @param array|string $complexNumber the complex number you want to raise to a power
* Or can be an array of values
* @param array|float|int|string $realNumber the power to which you want to raise the complex number
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMPOWER(array|string $complexNumber, array|float|int|string $realNumber): array|string
{
if (is_array($complexNumber) || is_array($realNumber)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException) {
return ExcelError::NAN();
}
if (!is_numeric($realNumber)) {
return ExcelError::VALUE();
}
return (string) $complex->pow((float) $realNumber);
}
}

View file

@ -0,0 +1,128 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ComplexOperations
{
use ArrayEnabled;
/**
* IMDIV.
*
* Returns the quotient of two complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMDIV(complexDividend,complexDivisor)
*
* @param array|string $complexDividend the complex numerator or dividend
* Or can be an array of values
* @param array|string $complexDivisor the complex denominator or divisor
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMDIV(array|string $complexDividend, array|string $complexDivisor): array|string
{
if (is_array($complexDividend) || is_array($complexDivisor)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexDividend, $complexDivisor);
}
try {
return (string) (new ComplexObject($complexDividend))->divideby(new ComplexObject($complexDivisor));
} catch (ComplexException) {
return ExcelError::NAN();
}
}
/**
* IMSUB.
*
* Returns the difference of two complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMSUB(complexNumber1,complexNumber2)
*
* @param array|string $complexNumber1 the complex number from which to subtract complexNumber2
* Or can be an array of values
* @param array|string $complexNumber2 the complex number to subtract from complexNumber1
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSUB(array|string $complexNumber1, array|string $complexNumber2): array|string
{
if (is_array($complexNumber1) || is_array($complexNumber2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber1, $complexNumber2);
}
try {
return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2));
} catch (ComplexException) {
return ExcelError::NAN();
}
}
/**
* IMSUM.
*
* Returns the sum of two or more complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMSUM(complexNumber[,complexNumber[,...]])
*
* @param string ...$complexNumbers Series of complex numbers to add
*/
public static function IMSUM(...$complexNumbers): string
{
// Return value
$returnValue = new ComplexObject(0.0);
$aArgs = Functions::flattenArray($complexNumbers);
try {
// Loop through the arguments
foreach ($aArgs as $complex) {
$returnValue = $returnValue->add(new ComplexObject($complex));
}
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $returnValue;
}
/**
* IMPRODUCT.
*
* Returns the product of two or more complex numbers in x + yi or x + yj text format.
*
* Excel Function:
* IMPRODUCT(complexNumber[,complexNumber[,...]])
*
* @param string ...$complexNumbers Series of complex numbers to multiply
*/
public static function IMPRODUCT(...$complexNumbers): string
{
// Return value
$returnValue = new ComplexObject(1.0);
$aArgs = Functions::flattenArray($complexNumbers);
try {
// Loop through the arguments
foreach ($aArgs as $complex) {
$returnValue = $returnValue->multiply(new ComplexObject($complex));
}
} catch (ComplexException) {
return ExcelError::NAN();
}
return (string) $returnValue;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
class Constants
{
/**
* EULER.
*/
public const EULER = 2.71828182845904523536;
}

View file

@ -0,0 +1,69 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
abstract class ConvertBase
{
use ArrayEnabled;
protected static function validateValue(mixed $value): string
{
if (is_bool($value)) {
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
throw new Exception(ExcelError::VALUE());
}
$value = (int) $value;
}
if (is_numeric($value)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
$value = floor((float) $value);
}
}
return strtoupper((string) $value);
}
protected static function validatePlaces(mixed $places = null): ?int
{
if ($places === null) {
return $places;
}
if (is_numeric($places)) {
if ($places < 0 || $places > 10) {
throw new Exception(ExcelError::NAN());
}
return (int) $places;
}
throw new Exception(ExcelError::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 ExcelError::NAN();
}
return substr($value, -10);
}
}

View file

@ -0,0 +1,163 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertBinary extends ConvertBase
{
/**
* toDecimal.
*
* Return a binary value as decimal.
*
* Excel Function:
* BIN2DEC(x)
*
* @param array|bool|float|int|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue($value);
$value = self::validateBinary($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10 && $value[0] === '1') {
// Two's Complement
$value = substr($value, -9);
return '-' . (512 - bindec($value));
}
return (string) bindec($value);
}
/**
* toHex.
*
* Return a binary value as hex.
*
* Excel Function:
* BIN2HEX(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param null|array|float|int|string $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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateBinary($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10 && $value[0] === '1') {
$high2 = substr($value, 0, 2);
$low8 = substr($value, 2);
$xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -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 array|bool|float|int|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.
* Or can be an array of values
* @param null|array|float|int|string $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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateBinary($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement
return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
}
$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(ExcelError::NAN());
}
return $value;
}
}

View file

@ -0,0 +1,213 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertDecimal extends ConvertBase
{
const LARGEST_OCTAL_IN_DECIMAL = 536870911;
const SMALLEST_OCTAL_IN_DECIMAL = -536870912;
const LARGEST_BINARY_IN_DECIMAL = 511;
const SMALLEST_BINARY_IN_DECIMAL = -512;
const LARGEST_HEX_IN_DECIMAL = 549755813887;
const SMALLEST_HEX_IN_DECIMAL = -549755813888;
/**
* toBinary.
*
* Return a decimal value as binary.
*
* Excel Function:
* DEC2BIN(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param null|array|float|int|string $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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) {
return ExcelError::NAN();
}
$r = decbin($value);
// Two's Complement
$r = substr($r, -10);
return self::nbrConversionFormat($r, $places);
}
/**
* toHex.
*
* Return a decimal value as hex.
*
* Excel Function:
* DEC2HEX(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param null|array|float|int|string $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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$value = floor((float) $value);
if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) {
return ExcelError::NAN();
}
$r = strtoupper(dechex((int) $value));
$r = self::hex32bit($value, $r);
return self::nbrConversionFormat($r, $places);
}
public static function hex32bit(float $value, string $hexstr, bool $force = false): string
{
if (PHP_INT_SIZE === 4 || $force) {
if ($value >= 2 ** 32) {
$quotient = (int) ($value / (2 ** 32));
return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr);
}
if ($value < -(2 ** 32)) {
$quotient = 256 - (int) ceil((-$value) / (2 ** 32));
return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8));
}
if ($value < 0) {
return "FF$hexstr";
}
}
return $hexstr;
}
/**
* toOctal.
*
* Return an decimal value as octal.
*
* Excel Function:
* DEC2OCT(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$value = (int) floor((float) $value);
if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) {
return ExcelError::NAN();
}
$r = decoct($value);
$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(ExcelError::VALUE());
}
return $value;
}
}

View file

@ -0,0 +1,175 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertHex extends ConvertBase
{
/**
* toBinary.
*
* Return a hex value as binary.
*
* Excel Function:
* HEX2BIN(x[,places])
*
* @param array|bool|float|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.
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateHex($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$dec = self::toDecimal($value);
return ConvertDecimal::toBinary($dec, $places);
}
/**
* toDecimal.
*
* Return a hex value as decimal.
*
* Excel Function:
* HEX2DEC(x)
*
* @param array|bool|float|int|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue($value);
$value = self::validateHex($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) > 10) {
return ExcelError::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 (string) ((bindec($binX) + 1) * -1);
}
return (string) bindec($binX);
}
/**
* toOctal.
*
* Return a hex value as octal.
*
* Excel Function:
* HEX2OCT(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateHex($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$decimal = self::toDecimal($value);
return ConvertDecimal::toOctal($decimal, $places);
}
protected static function validateHex(string $value): string
{
if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) {
throw new Exception(ExcelError::NAN());
}
return $value;
}
}

View file

@ -0,0 +1,174 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertOctal extends ConvertBase
{
/**
* toBinary.
*
* Return an octal value as binary.
*
* Excel Function:
* OCT2BIN(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateOctal($value);
$places = self::validatePlaces($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 array|bool|float|int|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue($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 (string) ((bindec($binX) + 1) * -1);
}
return (string) bindec($binX);
}
/**
* toHex.
*
* Return an octal value as hex.
*
* Excel Function:
* OCT2HEX(x[,places])
*
* @param array|bool|float|int|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.
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): array|string
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue($value);
$value = self::validateOctal($value);
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
$hexVal = strtoupper(dechex((int) self::toDecimal($value)));
$hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF{$hexVal}" : $hexVal;
return self::nbrConversionFormat($hexVal, $places);
}
protected static function validateOctal(string $value): string
{
$numDigits = (int) preg_match_all('/[01234567]/', $value);
if (strlen($value) > $numDigits || $numDigits > 10) {
throw new Exception(ExcelError::NAN());
}
return $value;
}
}

View file

@ -0,0 +1,679 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertUOM
{
use ArrayEnabled;
public const CATEGORY_WEIGHT_AND_MASS = 'Weight and Mass';
public const CATEGORY_DISTANCE = 'Distance';
public const CATEGORY_TIME = 'Time';
public const CATEGORY_PRESSURE = 'Pressure';
public const CATEGORY_FORCE = 'Force';
public const CATEGORY_ENERGY = 'Energy';
public const CATEGORY_POWER = 'Power';
public const CATEGORY_MAGNETISM = 'Magnetism';
public const CATEGORY_TEMPERATURE = 'Temperature';
public const CATEGORY_VOLUME = 'Volume and Liquid Measure';
public const CATEGORY_AREA = 'Area';
public const CATEGORY_INFORMATION = 'Information';
public const CATEGORY_SPEED = 'Speed';
/**
* Details of the Units of measure that can be used in CONVERTUOM().
*
* @var mixed[]
*/
private static array $conversionUnits = [
// Weight and Mass
'g' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true],
'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false],
'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false],
'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true],
'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false],
'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false],
'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false],
'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false],
'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
// Distance
'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true],
'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false],
'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false],
'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false],
'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false],
'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false],
'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true],
'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false],
'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false],
'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false],
'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false],
'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false],
'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false],
'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false],
'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false],
// Time
'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false],
'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false],
'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false],
'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false],
'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false],
'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false],
'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true],
's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true],
// Pressure
'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true],
'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true],
'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true],
'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true],
'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true],
'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true],
'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true],
// Force
'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true],
'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true],
'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true],
'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false],
'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true],
// Energy
'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true],
'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true],
'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true],
'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true],
'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true],
'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true],
'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false],
'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false],
'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true],
'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true],
'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false],
'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false],
'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false],
// Power
'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false],
'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false],
'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false],
// Magnetism
'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true],
'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true],
// Temperature
'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false],
'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false],
'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false],
'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false],
'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false],
'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false],
// Volume
'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false],
'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false],
'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false],
'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false],
'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false],
'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false],
'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false],
'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false],
'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false],
'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false],
'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false],
'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false],
'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true],
'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true],
'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false],
'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false],
'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false],
'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false],
'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false],
'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false],
'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false],
'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false],
'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true],
'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true],
'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false],
'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false],
'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false],
'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false],
'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false],
'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false],
'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false],
// Area
'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true],
'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false],
'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false],
'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true],
'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true],
'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true],
'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false],
'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false],
'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false],
'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false],
'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false],
'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false],
'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true],
'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true],
'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false],
'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false],
'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false],
'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false],
'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false],
'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false],
'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false],
// Information
'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true],
'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true],
// Speed
'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true],
'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true],
'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true],
'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true],
'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false],
'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false],
'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false],
];
/**
* Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
* @var mixed[]
*/
private static array $conversionMultipliers = [
'Y' => ['multiplier' => 1E24, 'name' => 'yotta'],
'Z' => ['multiplier' => 1E21, 'name' => 'zetta'],
'E' => ['multiplier' => 1E18, 'name' => 'exa'],
'P' => ['multiplier' => 1E15, 'name' => 'peta'],
'T' => ['multiplier' => 1E12, 'name' => 'tera'],
'G' => ['multiplier' => 1E9, 'name' => 'giga'],
'M' => ['multiplier' => 1E6, 'name' => 'mega'],
'k' => ['multiplier' => 1E3, 'name' => 'kilo'],
'h' => ['multiplier' => 1E2, 'name' => 'hecto'],
'e' => ['multiplier' => 1E1, 'name' => 'dekao'],
'da' => ['multiplier' => 1E1, 'name' => 'dekao'],
'd' => ['multiplier' => 1E-1, 'name' => 'deci'],
'c' => ['multiplier' => 1E-2, 'name' => 'centi'],
'm' => ['multiplier' => 1E-3, 'name' => 'milli'],
'u' => ['multiplier' => 1E-6, 'name' => 'micro'],
'n' => ['multiplier' => 1E-9, 'name' => 'nano'],
'p' => ['multiplier' => 1E-12, 'name' => 'pico'],
'f' => ['multiplier' => 1E-15, 'name' => 'femto'],
'a' => ['multiplier' => 1E-18, 'name' => 'atto'],
'z' => ['multiplier' => 1E-21, 'name' => 'zepto'],
'y' => ['multiplier' => 1E-24, 'name' => 'yocto'],
];
/**
* Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
* @var mixed[]
*/
private static array $binaryConversionMultipliers = [
'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'],
'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'],
'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'],
'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'],
'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'],
'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'],
'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'],
'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'],
];
/**
* Details of the Units of measure conversion factors, organised by group.
*
* @var mixed[]
*/
private static array $unitConversions = [
// Conversion uses gram (g) as an intermediate unit
self::CATEGORY_WEIGHT_AND_MASS => [
'g' => 1.0,
'sg' => 6.85217658567918E-05,
'lbm' => 2.20462262184878E-03,
'u' => 6.02214179421676E+23,
'ozm' => 3.52739619495804E-02,
'grain' => 1.54323583529414E+01,
'cwt' => 2.20462262184878E-05,
'shweight' => 2.20462262184878E-05,
'uk_cwt' => 1.96841305522212E-05,
'lcwt' => 1.96841305522212E-05,
'hweight' => 1.96841305522212E-05,
'stone' => 1.57473044417770E-04,
'ton' => 1.10231131092439E-06,
'uk_ton' => 9.84206527611061E-07,
'LTON' => 9.84206527611061E-07,
'brton' => 9.84206527611061E-07,
],
// Conversion uses meter (m) as an intermediate unit
self::CATEGORY_DISTANCE => [
'm' => 1.0,
'mi' => 6.21371192237334E-04,
'Nmi' => 5.39956803455724E-04,
'in' => 3.93700787401575E+01,
'ft' => 3.28083989501312E+00,
'yd' => 1.09361329833771E+00,
'ang' => 1.0E+10,
'ell' => 8.74890638670166E-01,
'ly' => 1.05700083402462E-16,
'parsec' => 3.24077928966473E-17,
'pc' => 3.24077928966473E-17,
'Pica' => 2.83464566929134E+03,
'Picapt' => 2.83464566929134E+03,
'pica' => 2.36220472440945E+02,
'survey_mi' => 6.21369949494950E-04,
],
// Conversion uses second (s) as an intermediate unit
self::CATEGORY_TIME => [
'yr' => 3.16880878140289E-08,
'day' => 1.15740740740741E-05,
'd' => 1.15740740740741E-05,
'hr' => 2.77777777777778E-04,
'mn' => 1.66666666666667E-02,
'min' => 1.66666666666667E-02,
'sec' => 1.0,
's' => 1.0,
],
// Conversion uses Pascal (Pa) as an intermediate unit
self::CATEGORY_PRESSURE => [
'Pa' => 1.0,
'p' => 1.0,
'atm' => 9.86923266716013E-06,
'at' => 9.86923266716013E-06,
'mmHg' => 7.50063755419211E-03,
'psi' => 1.45037737730209E-04,
'Torr' => 7.50061682704170E-03,
],
// Conversion uses Newton (N) as an intermediate unit
self::CATEGORY_FORCE => [
'N' => 1.0,
'dyn' => 1.0E+5,
'dy' => 1.0E+5,
'lbf' => 2.24808923655339E-01,
'pond' => 1.01971621297793E+02,
],
// Conversion uses Joule (J) as an intermediate unit
self::CATEGORY_ENERGY => [
'J' => 1.0,
'e' => 9.99999519343231E+06,
'c' => 2.39006249473467E-01,
'cal' => 2.38846190642017E-01,
'eV' => 6.24145700000000E+18,
'ev' => 6.24145700000000E+18,
'HPh' => 3.72506430801000E-07,
'hh' => 3.72506430801000E-07,
'Wh' => 2.77777916238711E-04,
'wh' => 2.77777916238711E-04,
'flb' => 2.37304222192651E+01,
'BTU' => 9.47815067349015E-04,
'btu' => 9.47815067349015E-04,
],
// Conversion uses Horsepower (HP) as an intermediate unit
self::CATEGORY_POWER => [
'HP' => 1.0,
'h' => 1.0,
'W' => 7.45699871582270E+02,
'w' => 7.45699871582270E+02,
'PS' => 1.01386966542400E+00,
],
// Conversion uses Tesla (T) as an intermediate unit
self::CATEGORY_MAGNETISM => [
'T' => 1.0,
'ga' => 10000.0,
],
// Conversion uses litre (l) as an intermediate unit
self::CATEGORY_VOLUME => [
'l' => 1.0,
'L' => 1.0,
'lt' => 1.0,
'tsp' => 2.02884136211058E+02,
'tspm' => 2.0E+02,
'tbs' => 6.76280454036860E+01,
'oz' => 3.38140227018430E+01,
'cup' => 4.22675283773038E+00,
'pt' => 2.11337641886519E+00,
'us_pt' => 2.11337641886519E+00,
'uk_pt' => 1.75975398639270E+00,
'qt' => 1.05668820943259E+00,
'uk_qt' => 8.79876993196351E-01,
'gal' => 2.64172052358148E-01,
'uk_gal' => 2.19969248299088E-01,
'ang3' => 1.0E+27,
'ang^3' => 1.0E+27,
'barrel' => 6.28981077043211E-03,
'bushel' => 2.83775932584017E-02,
'in3' => 6.10237440947323E+01,
'in^3' => 6.10237440947323E+01,
'ft3' => 3.53146667214886E-02,
'ft^3' => 3.53146667214886E-02,
'ly3' => 1.18093498844171E-51,
'ly^3' => 1.18093498844171E-51,
'm3' => 1.0E-03,
'm^3' => 1.0E-03,
'mi3' => 2.39912758578928E-13,
'mi^3' => 2.39912758578928E-13,
'yd3' => 1.30795061931439E-03,
'yd^3' => 1.30795061931439E-03,
'Nmi3' => 1.57426214685811E-13,
'Nmi^3' => 1.57426214685811E-13,
'Pica3' => 2.27769904358706E+07,
'Pica^3' => 2.27769904358706E+07,
'Picapt3' => 2.27769904358706E+07,
'Picapt^3' => 2.27769904358706E+07,
'GRT' => 3.53146667214886E-04,
'regton' => 3.53146667214886E-04,
'MTON' => 8.82866668037215E-04,
],
// Conversion uses hectare (ha) as an intermediate unit
self::CATEGORY_AREA => [
'ha' => 1.0,
'uk_acre' => 2.47105381467165E+00,
'us_acre' => 2.47104393046628E+00,
'ang2' => 1.0E+24,
'ang^2' => 1.0E+24,
'ar' => 1.0E+02,
'ft2' => 1.07639104167097E+05,
'ft^2' => 1.07639104167097E+05,
'in2' => 1.55000310000620E+07,
'in^2' => 1.55000310000620E+07,
'ly2' => 1.11725076312873E-28,
'ly^2' => 1.11725076312873E-28,
'm2' => 1.0E+04,
'm^2' => 1.0E+04,
'Morgen' => 4.0E+00,
'mi2' => 3.86102158542446E-03,
'mi^2' => 3.86102158542446E-03,
'Nmi2' => 2.91553349598123E-03,
'Nmi^2' => 2.91553349598123E-03,
'Pica2' => 8.03521607043214E+10,
'Pica^2' => 8.03521607043214E+10,
'Picapt2' => 8.03521607043214E+10,
'Picapt^2' => 8.03521607043214E+10,
'yd2' => 1.19599004630108E+04,
'yd^2' => 1.19599004630108E+04,
],
// Conversion uses bit (bit) as an intermediate unit
self::CATEGORY_INFORMATION => [
'bit' => 1.0,
'byte' => 0.125,
],
// Conversion uses Meters per Second (m/s) as an intermediate unit
self::CATEGORY_SPEED => [
'm/s' => 1.0,
'm/sec' => 1.0,
'm/h' => 3.60E+03,
'm/hr' => 3.60E+03,
'mph' => 2.23693629205440E+00,
'admkn' => 1.94260256941567E+00,
'kn' => 1.94384449244060E+00,
],
];
/**
* getConversionGroups
* Returns a list of the different conversion groups for UOM conversions.
*/
public static function getConversionCategories(): array
{
$conversionGroups = [];
foreach (self::$conversionUnits as $conversionUnit) {
$conversionGroups[] = $conversionUnit['Group'];
}
return array_merge(array_unique($conversionGroups));
}
/**
* getConversionGroupUnits
* Returns an array of units of measure, for a specified conversion group, or for all groups.
*
* @param ?string $category The group whose units of measure you want to retrieve
*/
public static function getConversionCategoryUnits(?string $category = null): array
{
$conversionGroups = [];
foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
if (($category === null) || ($conversionGroup['Group'] == $category)) {
$conversionGroups[$conversionGroup['Group']][] = $conversionUnit;
}
}
return $conversionGroups;
}
/**
* getConversionGroupUnitDetails.
*
* @param ?string $category The group whose units of measure you want to retrieve
*/
public static function getConversionCategoryUnitDetails(?string $category = null): array
{
$conversionGroups = [];
foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
if (($category === null) || ($conversionGroup['Group'] == $category)) {
$conversionGroups[$conversionGroup['Group']][] = [
'unit' => $conversionUnit,
'description' => $conversionGroup['Unit Name'],
];
}
}
return $conversionGroups;
}
/**
* getConversionMultipliers
* Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
* @return mixed[]
*/
public static function getConversionMultipliers(): array
{
return self::$conversionMultipliers;
}
/**
* getBinaryConversionMultipliers
* Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM().
*
* @return mixed[]
*/
public static function getBinaryConversionMultipliers(): array
{
return self::$binaryConversionMultipliers;
}
/**
* CONVERT.
*
* Converts a number from one measurement system to another.
* For example, CONVERT can translate a table of distances in miles to a table of distances
* in kilometers.
*
* Excel Function:
* CONVERT(value,fromUOM,toUOM)
*
* @param array|float|int|string $value the value in fromUOM to convert
* Or can be an array of values
* @param array|string $fromUOM the units for value
* Or can be an array of values
* @param array|string $toUOM the units for the result
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function CONVERT($value, $fromUOM, $toUOM)
{
if (is_array($value) || is_array($fromUOM) || is_array($toUOM)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $fromUOM, $toUOM);
}
if (!is_numeric($value)) {
return ExcelError::VALUE();
}
try {
[$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM);
[$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM);
} catch (Exception) {
return ExcelError::NA();
}
if ($fromCategory !== $toCategory) {
return ExcelError::NA();
}
// @var float $value
$value *= $fromMultiplier;
if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) {
// We've already factored $fromMultiplier into the value, so we need
// to reverse it again
return $value / $fromMultiplier;
} elseif ($fromUOM === $toUOM) {
return $value / $toMultiplier;
} elseif ($fromCategory === self::CATEGORY_TEMPERATURE) {
return self::convertTemperature($fromUOM, $toUOM, $value);
}
$baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]);
return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier;
}
private static function getUOMDetails(string $uom): array
{
if (isset(self::$conversionUnits[$uom])) {
$unitCategory = self::$conversionUnits[$uom]['Group'];
return [$uom, $unitCategory, 1.0];
}
// Check 1-character standard metric multiplier prefixes
$multiplierType = substr($uom, 0, 1);
$uom = substr($uom, 1);
if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
throw new Exception('Prefix not allowed for UoM');
}
$unitCategory = self::$conversionUnits[$uom]['Group'];
return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
}
$multiplierType .= substr($uom, 0, 1);
$uom = substr($uom, 1);
// Check 2-character standard metric multiplier prefixes
if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
throw new Exception('Prefix not allowed for UoM');
}
$unitCategory = self::$conversionUnits[$uom]['Group'];
return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
}
// Check 2-character binary multiplier prefixes
if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) {
if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
throw new Exception('Prefix not allowed for UoM');
}
$unitCategory = self::$conversionUnits[$uom]['Group'];
if ($unitCategory !== 'Information') {
throw new Exception('Binary Prefix is only allowed for Information UoM');
}
return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']];
}
throw new Exception('UoM Not Found');
}
protected static function convertTemperature(string $fromUOM, string $toUOM, float|int $value): float|int
{
$fromUOM = self::resolveTemperatureSynonyms($fromUOM);
$toUOM = self::resolveTemperatureSynonyms($toUOM);
if ($fromUOM === $toUOM) {
return $value;
}
// Convert to Kelvin
switch ($fromUOM) {
case 'F':
$value = ($value - 32) / 1.8 + 273.15;
break;
case 'C':
$value += 273.15;
break;
case 'Rank':
$value /= 1.8;
break;
case 'Reau':
$value = $value * 1.25 + 273.15;
break;
}
// Convert from Kelvin
switch ($toUOM) {
case 'F':
$value = ($value - 273.15) * 1.8 + 32.00;
break;
case 'C':
$value -= 273.15;
break;
case 'Rank':
$value *= 1.8;
break;
case 'Reau':
$value = ($value - 273.15) * 0.80000;
break;
}
return $value;
}
private static function resolveTemperatureSynonyms(string $uom): string
{
return match ($uom) {
'fah' => 'F',
'cel' => 'C',
'kel' => 'K',
default => $uom,
};
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class EngineeringValidations
{
public static function validateFloat(mixed $value): float
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (float) $value;
}
public static function validateInt(mixed $value): int
{
if (!is_numeric($value)) {
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Erf
{
use ArrayEnabled;
private const TWO_SQRT_PI = 1.128379167095512574;
/**
* ERF.
*
* Returns the error function integrated between the lower and upper bound arguments.
*
* Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments,
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
* improved, so that it can now calculate the function for both positive and negative ranges.
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments.
*
* Excel Function:
* ERF(lower[,upper])
*
* @param mixed $lower Lower bound float for integrating ERF
* Or can be an array of values
* @param mixed $upper Upper bound float for integrating ERF.
* If omitted, ERF integrates between zero and lower_limit
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERF(mixed $lower, mixed $upper = null): array|float|string
{
if (is_array($lower) || is_array($upper)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $lower, $upper);
}
if (is_numeric($lower)) {
if ($upper === null) {
return self::erfValue($lower);
}
if (is_numeric($upper)) {
return self::erfValue($upper) - self::erfValue($lower);
}
}
return ExcelError::VALUE();
}
/**
* ERFPRECISE.
*
* Returns the error function integrated between the lower and upper bound arguments.
*
* Excel Function:
* ERF.PRECISE(limit)
*
* @param mixed $limit Float bound for integrating ERF, other bound is zero
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFPRECISE(mixed $limit)
{
if (is_array($limit)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $limit);
}
return self::ERF($limit);
}
private static function makeFloat(mixed $value): float
{
return is_numeric($value) ? ((float) $value) : 0.0;
}
/**
* Method to calculate the erf value.
*/
public static function erfValue(float|int|string $value): float
{
$value = (float) $value;
if (abs($value) > 2.2) {
return 1 - self::makeFloat(ErfC::ERFC($value));
}
$sum = $term = $value;
$xsqr = ($value * $value);
$j = 1;
do {
$term *= $xsqr / $j;
$sum -= $term / (2 * $j + 1);
++$j;
$term *= $xsqr / $j;
$sum += $term / (2 * $j + 1);
++$j;
if ($sum == 0.0) {
break;
}
} while (abs($term / $sum) > Functions::PRECISION);
return self::TWO_SQRT_PI * $sum;
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ErfC
{
use ArrayEnabled;
/**
* ERFC.
*
* Returns the complementary ERF function integrated between x and infinity
*
* Note: In Excel 2007 or earlier, if you input a negative value for the lower bound argument,
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
* improved, so that it can now calculate the function for both positive and negative x values.
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments.
*
* Excel Function:
* ERFC(x)
*
* @param mixed $value The float lower bound for integrating ERFC
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFC(mixed $value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (is_numeric($value)) {
return self::erfcValue($value);
}
return ExcelError::VALUE();
}
private const ONE_SQRT_PI = 0.564189583547756287;
/**
* Method to calculate the erfc value.
*/
private static function erfcValue(float|int|string $value): float|int
{
$value = (float) $value;
if (abs($value) < 2.2) {
return 1 - Erf::erfValue($value);
}
if ($value < 0) {
return 2 - self::erfcValue(-$value);
}
$a = $n = 1;
$b = $c = $value;
$d = ($value * $value) + 0.5;
$q2 = $b / $d;
do {
$t = $a * $n + $b * $value;
$a = $b;
$b = $t;
$t = $c * $n + $d * $value;
$c = $d;
$d = $t;
$n += 0.5;
$q1 = $q2;
$q2 = $b / $d;
} while ((abs($q1 - $q2) / $q2) > Functions::PRECISION);
return self::ONE_SQRT_PI * exp(-$value * $value) * $q2;
}
}