class NativeCalculator
Calculator implementation using only native PHP code.
@internal
@psalm-immutable
Hierarchy
- class \Brick\Math\Internal\Calculator
- class \Brick\Math\Internal\Calculator\NativeCalculator extends \Brick\Math\Internal\Calculator
Expanded class hierarchy of NativeCalculator
File
-
vendor/
brick/ math/ src/ Internal/ Calculator/ NativeCalculator.php, line 16
Namespace
Brick\Math\Internal\CalculatorView source
class NativeCalculator extends Calculator {
/**
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
* For multiplication, this represents the max sum of the lengths of both operands.
*
* In addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
*/
private readonly int $maxDigits;
/**
* @codeCoverageIgnore
*/
public function __construct() {
$this->maxDigits = match (PHP_INT_SIZE) { 4 => 9,
8 => 18,
default => throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'),
};
}
public function add(string $a, string $b) : string {
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
*/
$result = $a + $b;
if (is_int($result)) {
return (string) $result;
}
if ($a === '0') {
return $b;
}
if ($b === '0') {
return $a;
}
[
$aNeg,
$bNeg,
$aDig,
$bDig,
] = $this->init($a, $b);
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
if ($aNeg) {
$result = $this->neg($result);
}
return $result;
}
public function sub(string $a, string $b) : string {
return $this->add($a, $this->neg($b));
}
public function mul(string $a, string $b) : string {
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
*/
$result = $a * $b;
if (is_int($result)) {
return (string) $result;
}
if ($a === '0' || $b === '0') {
return '0';
}
if ($a === '1') {
return $b;
}
if ($b === '1') {
return $a;
}
if ($a === '-1') {
return $this->neg($b);
}
if ($b === '-1') {
return $this->neg($a);
}
[
$aNeg,
$bNeg,
$aDig,
$bDig,
] = $this->init($a, $b);
$result = $this->doMul($aDig, $bDig);
if ($aNeg !== $bNeg) {
$result = $this->neg($result);
}
return $result;
}
public function divQ(string $a, string $b) : string {
return $this->divQR($a, $b)[0];
}
public function divR(string $a, string $b) : string {
return $this->divQR($a, $b)[1];
}
public function divQR(string $a, string $b) : array {
if ($a === '0') {
return [
'0',
'0',
];
}
if ($a === $b) {
return [
'1',
'0',
];
}
if ($b === '1') {
return [
$a,
'0',
];
}
if ($b === '-1') {
return [
$this->neg($a),
'0',
];
}
/** @psalm-var numeric-string $a */
$na = $a * 1;
// cast to number
if (is_int($na)) {
/** @psalm-var numeric-string $b */
$nb = $b * 1;
if (is_int($nb)) {
// the only division that may overflow is PHP_INT_MIN / -1,
// which cannot happen here as we've already handled a divisor of -1 above.
$q = intdiv($na, $nb);
$r = $na % $nb;
return [
(string) $q,
(string) $r,
];
}
}
[
$aNeg,
$bNeg,
$aDig,
$bDig,
] = $this->init($a, $b);
[
$q,
$r,
] = $this->doDiv($aDig, $bDig);
if ($aNeg !== $bNeg) {
$q = $this->neg($q);
}
if ($aNeg) {
$r = $this->neg($r);
}
return [
$q,
$r,
];
}
public function pow(string $a, int $e) : string {
if ($e === 0) {
return '1';
}
if ($e === 1) {
return $a;
}
$odd = $e % 2;
$e -= $odd;
$aa = $this->mul($a, $a);
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
$result = $this->pow($aa, $e / 2);
if ($odd === 1) {
$result = $this->mul($result, $a);
}
return $result;
}
/**
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
*/
public function modPow(string $base, string $exp, string $mod) : string {
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
if ($base === '0' && $exp === '0' && $mod === '1') {
return '0';
}
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
if ($exp === '0' && $mod === '1') {
return '0';
}
$x = $base;
$res = '1';
// numbers are positive, so we can use remainder instead of modulo
$x = $this->divR($x, $mod);
while ($exp !== '0') {
if (in_array($exp[-1], [
'1',
'3',
'5',
'7',
'9',
])) {
// odd
$res = $this->divR($this->mul($res, $x), $mod);
}
$exp = $this->divQ($exp, '2');
$x = $this->divR($this->mul($x, $x), $mod);
}
return $res;
}
/**
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
*/
public function sqrt(string $n) : string {
if ($n === '0') {
return '0';
}
// initial approximation
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
$decreased = false;
for (;;) {
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
break;
}
$decreased = $this->cmp($nx, $x) < 0;
$x = $nx;
}
return $x;
}
/**
* Performs the addition of two non-signed large integers.
*/
private function doAdd(string $a, string $b) : string {
[
$a,
$b,
$length,
] = $this->pad($a, $b);
$carry = 0;
$result = '';
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
$blockLength = $this->maxDigits;
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = (string) ($blockA + $blockB + $carry);
$sumLength = \strlen($sum);
if ($sumLength > $blockLength) {
$sum = \substr($sum, 1);
$carry = 1;
}
else {
if ($sumLength < $blockLength) {
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
}
$carry = 0;
}
$result = $sum . $result;
if ($i === 0) {
break;
}
}
if ($carry === 1) {
$result = '1' . $result;
}
return $result;
}
/**
* Performs the subtraction of two non-signed large integers.
*/
private function doSub(string $a, string $b) : string {
if ($a === $b) {
return '0';
}
// Ensure that we always subtract to a positive result: biggest minus smallest.
$cmp = $this->doCmp($a, $b);
$invert = $cmp === -1;
if ($invert) {
$c = $a;
$a = $b;
$b = $c;
}
[
$a,
$b,
$length,
] = $this->pad($a, $b);
$carry = 0;
$result = '';
$complement = 10 ** $this->maxDigits;
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
$blockLength = $this->maxDigits;
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = $blockA - $blockB - $carry;
if ($sum < 0) {
$sum += $complement;
$carry = 1;
}
else {
$carry = 0;
}
$sum = (string) $sum;
$sumLength = \strlen($sum);
if ($sumLength < $blockLength) {
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
}
$result = $sum . $result;
if ($i === 0) {
break;
}
}
// Carry cannot be 1 when the loop ends, as a > b
assert($carry === 0);
$result = \ltrim($result, '0');
if ($invert) {
$result = $this->neg($result);
}
return $result;
}
/**
* Performs the multiplication of two non-signed large integers.
*/
private function doMul(string $a, string $b) : string {
$x = \strlen($a);
$y = \strlen($b);
$maxDigits = \intdiv($this->maxDigits, 2);
$complement = 10 ** $maxDigits;
$result = '0';
for ($i = $x - $maxDigits;; $i -= $maxDigits) {
$blockALength = $maxDigits;
if ($i < 0) {
$blockALength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
$blockA = (int) \substr($a, $i, $blockALength);
$line = '';
$carry = 0;
for ($j = $y - $maxDigits;; $j -= $maxDigits) {
$blockBLength = $maxDigits;
if ($j < 0) {
$blockBLength += $j;
/** @psalm-suppress LoopInvalidation */
$j = 0;
}
$blockB = (int) \substr($b, $j, $blockBLength);
$mul = $blockA * $blockB + $carry;
$value = $mul % $complement;
$carry = ($mul - $value) / $complement;
$value = (string) $value;
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
$line = $value . $line;
if ($j === 0) {
break;
}
}
if ($carry !== 0) {
$line = $carry . $line;
}
$line = \ltrim($line, '0');
if ($line !== '') {
$line .= \str_repeat('0', $x - $blockALength - $i);
$result = $this->add($result, $line);
}
if ($i === 0) {
break;
}
}
return $result;
}
/**
* Performs the division of two non-signed large integers.
*
* @return string[] The quotient and remainder.
*/
private function doDiv(string $a, string $b) : array {
$cmp = $this->doCmp($a, $b);
if ($cmp === -1) {
return [
'0',
$a,
];
}
$x = \strlen($a);
$y = \strlen($b);
// we now know that a >= b && x >= y
$q = '0';
// quotient
$r = $a;
// remainder
$z = $y;
// focus length, always $y or $y+1
for (;;) {
$focus = \substr($a, 0, $z);
$cmp = $this->doCmp($focus, $b);
if ($cmp === -1) {
if ($z === $x) {
// remainder < dividend
break;
}
$z++;
}
$zeros = \str_repeat('0', $x - $z);
$q = $this->add($q, '1' . $zeros);
$a = $this->sub($a, $b . $zeros);
$r = $a;
if ($r === '0') {
// remainder == 0
break;
}
$x = \strlen($a);
if ($x < $y) {
// remainder < dividend
break;
}
$z = $y;
}
return [
$q,
$r,
];
}
/**
* Compares two non-signed large numbers.
*
* @psalm-return -1|0|1
*/
private function doCmp(string $a, string $b) : int {
$x = \strlen($a);
$y = \strlen($b);
$cmp = $x <=> $y;
if ($cmp !== 0) {
return $cmp;
}
return \strcmp($a, $b) <=> 0;
// enforce -1|0|1
}
/**
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
*
* The numbers must only consist of digits, without leading minus sign.
*
* @return array{string, string, int}
*/
private function pad(string $a, string $b) : array {
$x = \strlen($a);
$y = \strlen($b);
if ($x > $y) {
$b = \str_repeat('0', $x - $y) . $b;
return [
$a,
$b,
$x,
];
}
if ($x < $y) {
$a = \str_repeat('0', $y - $x) . $a;
return [
$a,
$b,
$y,
];
}
return [
$a,
$b,
$x,
];
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overriden Title | Overrides |
---|---|---|---|---|---|
Calculator::$instance | private static | property | The Calculator instance in use. | ||
Calculator::abs | final public | function | Returns the absolute value of a number. | ||
Calculator::ALPHABET | public | constant | The alphabet for converting from and to base 2 to 36, lowercase. | ||
Calculator::and | public | function | Calculates bitwise AND of two numbers. | 1 | |
Calculator::bitwise | private | function | Performs a bitwise operation on a decimal number. | ||
Calculator::cmp | final public | function | Compares two numbers. | ||
Calculator::detect | private static | function | Returns the fastest available Calculator implementation. | ||
Calculator::divRound | final public | function | Performs a rounded division. | ||
Calculator::fromArbitraryBase | final public | function | Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. | ||
Calculator::fromBase | public | function | Converts a number from an arbitrary base. | 1 | |
Calculator::gcd | public | function | Returns the greatest common divisor of the two numbers. | 1 | |
Calculator::gcdExtended | private | function | |||
Calculator::get | final public static | function | Returns the Calculator instance to use. | ||
Calculator::init | final protected | function | Extracts the sign & digits of the operands. | ||
Calculator::MAX_POWER | public | constant | The maximum exponent value allowed for the pow() method. | ||
Calculator::mod | public | function | |||
Calculator::modInverse | public | function | Returns the modular multiplicative inverse of $x modulo $m. | 1 | |
Calculator::neg | final public | function | Negates a number. | ||
Calculator::or | public | function | Calculates bitwise OR of two numbers. | 1 | |
Calculator::set | final public static | function | Sets the Calculator instance to use. | ||
Calculator::toArbitraryBase | final public | function | Converts a non-negative number to an arbitrary base using a custom alphabet. | ||
Calculator::toBase | public | function | Converts a number to an arbitrary base. | 1 | |
Calculator::toBinary | private | function | Converts a decimal number to a binary string. | ||
Calculator::toDecimal | private | function | Returns the positive decimal representation of a binary number. | ||
Calculator::twosComplement | private | function | |||
Calculator::xor | public | function | Calculates bitwise XOR of two numbers. | 1 | |
NativeCalculator::$maxDigits | private | property | The max number of digits the platform can natively add, subtract, multiply or divide without overflow. For multiplication, this represents the max sum of the lengths of both operands. |
||
NativeCalculator::add | public | function | Adds two numbers. | Overrides Calculator::add | |
NativeCalculator::divQ | public | function | Returns the quotient of the division of two numbers. | Overrides Calculator::divQ | |
NativeCalculator::divQR | public | function | Returns the quotient and remainder of the division of two numbers. | Overrides Calculator::divQR | |
NativeCalculator::divR | public | function | Returns the remainder of the division of two numbers. | Overrides Calculator::divR | |
NativeCalculator::doAdd | private | function | Performs the addition of two non-signed large integers. | ||
NativeCalculator::doCmp | private | function | Compares two non-signed large numbers. | ||
NativeCalculator::doDiv | private | function | Performs the division of two non-signed large integers. | ||
NativeCalculator::doMul | private | function | Performs the multiplication of two non-signed large integers. | ||
NativeCalculator::doSub | private | function | Performs the subtraction of two non-signed large integers. | ||
NativeCalculator::modPow | public | function | Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ | Overrides Calculator::modPow | |
NativeCalculator::mul | public | function | Multiplies two numbers. | Overrides Calculator::mul | |
NativeCalculator::pad | private | function | Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length. | ||
NativeCalculator::pow | public | function | Exponentiates a number. | Overrides Calculator::pow | |
NativeCalculator::sqrt | public | function | Adapted from https://cp-algorithms.com/num_methods/roots_newton.html | Overrides Calculator::sqrt | |
NativeCalculator::sub | public | function | Subtracts two numbers. | Overrides Calculator::sub | |
NativeCalculator::__construct | public | function | @codeCoverageIgnore |