class Calculator
Same name in this branch
- 11.1.x vendor/sebastian/complexity/src/Calculator.php \SebastianBergmann\Complexity\Calculator
Performs basic operations on arbitrary size integers.
Unless otherwise specified, all parameters must be validated as non-empty strings of digits, without leading zero, and with an optional leading minus sign if the number is not zero.
Any other parameter format will lead to undefined behaviour. All methods must return strings respecting this format, unless specified otherwise.
@internal
@psalm-immutable
Hierarchy
- class \Brick\Math\Internal\Calculator
Expanded class hierarchy of Calculator
5 files declare their use of Calculator
- BcMathCalculator.php in vendor/
brick/ math/ src/ Internal/ Calculator/ BcMathCalculator.php - BigDecimal.php in vendor/
brick/ math/ src/ BigDecimal.php - BigInteger.php in vendor/
brick/ math/ src/ BigInteger.php - GmpCalculator.php in vendor/
brick/ math/ src/ Internal/ Calculator/ GmpCalculator.php - NativeCalculator.php in vendor/
brick/ math/ src/ Internal/ Calculator/ NativeCalculator.php
File
-
vendor/
brick/ math/ src/ Internal/ Calculator.php, line 23
Namespace
Brick\Math\InternalView source
abstract class Calculator {
/**
* The maximum exponent value allowed for the pow() method.
*/
public const MAX_POWER = 1000000;
/**
* The alphabet for converting from and to base 2 to 36, lowercase.
*/
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* Sets the Calculator instance to use.
*
* An instance is typically set only in unit tests: the autodetect is usually the best option.
*
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
*/
public static final function set(?Calculator $calculator) : void {
self::$instance = $calculator;
}
/**
* Returns the Calculator instance to use.
*
* If none has been explicitly set, the fastest available implementation will be returned.
*
* @psalm-pure
* @psalm-suppress ImpureStaticProperty
*/
public static final function get() : Calculator {
if (self::$instance === null) {
/** @psalm-suppress ImpureMethodCall */
self::$instance = self::detect();
}
return self::$instance;
}
/**
* Returns the fastest available Calculator implementation.
*
* @codeCoverageIgnore
*/
private static function detect() : Calculator {
if (\extension_loaded('gmp')) {
return new Calculator\GmpCalculator();
}
if (\extension_loaded('bcmath')) {
return new Calculator\BcMathCalculator();
}
return new Calculator\NativeCalculator();
}
/**
* Extracts the sign & digits of the operands.
*
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
*/
protected final function init(string $a, string $b) : array {
return [
$aNeg = $a[0] === '-',
$bNeg = $b[0] === '-',
$aNeg ? \substr($a, 1) : $a,
$bNeg ? \substr($b, 1) : $b,
];
}
/**
* Returns the absolute value of a number.
*/
public final function abs(string $n) : string {
return $n[0] === '-' ? \substr($n, 1) : $n;
}
/**
* Negates a number.
*/
public final function neg(string $n) : string {
if ($n === '0') {
return '0';
}
if ($n[0] === '-') {
return \substr($n, 1);
}
return '-' . $n;
}
/**
* Compares two numbers.
*
* @psalm-return -1|0|1
*
* @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
*/
public final function cmp(string $a, string $b) : int {
[
$aNeg,
$bNeg,
$aDig,
$bDig,
] = $this->init($a, $b);
if ($aNeg && !$bNeg) {
return -1;
}
if ($bNeg && !$aNeg) {
return 1;
}
$aLen = \strlen($aDig);
$bLen = \strlen($bDig);
if ($aLen < $bLen) {
$result = -1;
}
elseif ($aLen > $bLen) {
$result = 1;
}
else {
$result = $aDig <=> $bDig;
}
return $aNeg ? -$result : $result;
}
/**
* Adds two numbers.
*/
public abstract function add(string $a, string $b) : string;
/**
* Subtracts two numbers.
*/
public abstract function sub(string $a, string $b) : string;
/**
* Multiplies two numbers.
*/
public abstract function mul(string $a, string $b) : string;
/**
* Returns the quotient of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return string The quotient.
*/
public abstract function divQ(string $a, string $b) : string;
/**
* Returns the remainder of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return string The remainder.
*/
public abstract function divR(string $a, string $b) : string;
/**
* Returns the quotient and remainder of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return array{string, string} An array containing the quotient and remainder.
*/
public abstract function divQR(string $a, string $b) : array;
/**
* Exponentiates a number.
*
* @param string $a The base number.
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
*
* @return string The power.
*/
public abstract function pow(string $a, int $e) : string;
/**
* @param string $b The modulus; must not be zero.
*/
public function mod(string $a, string $b) : string {
return $this->divR($this->add($this->divR($a, $b), $b), $b);
}
/**
* Returns the modular multiplicative inverse of $x modulo $m.
*
* If $x has no multiplicative inverse mod m, this method must return null.
*
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
*
* @param string $m The modulus; must not be negative or zero.
*/
public function modInverse(string $x, string $m) : ?string {
if ($m === '1') {
return '0';
}
$modVal = $x;
if ($x[0] === '-' || $this->cmp($this->abs($x), $m) >= 0) {
$modVal = $this->mod($x, $m);
}
[
$g,
$x,
] = $this->gcdExtended($modVal, $m);
if ($g !== '1') {
return null;
}
return $this->mod($this->add($this->mod($x, $m), $m), $m);
}
/**
* Raises a number into power with modulo.
*
* @param string $base The base number; must be positive or zero.
* @param string $exp The exponent; must be positive or zero.
* @param string $mod The modulus; must be strictly positive.
*/
public abstract function modPow(string $base, string $exp, string $mod) : string;
/**
* Returns the greatest common divisor of the two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for GCD calculations.
*
* @return string The GCD, always positive, or zero if both arguments are zero.
*/
public function gcd(string $a, string $b) : string {
if ($a === '0') {
return $this->abs($b);
}
if ($b === '0') {
return $this->abs($a);
}
return $this->gcd($b, $this->divR($a, $b));
}
/**
* @return array{string, string, string} GCD, X, Y
*/
private function gcdExtended(string $a, string $b) : array {
if ($a === '0') {
return [
$b,
'0',
'1',
];
}
[
$gcd,
$x1,
$y1,
] = $this->gcdExtended($this->mod($b, $a), $a);
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
$y = $x1;
return [
$gcd,
$x,
$y,
];
}
/**
* Returns the square root of the given number, rounded down.
*
* The result is the largest x such that x² ≤ n.
* The input MUST NOT be negative.
*/
public abstract function sqrt(string $n) : string;
/**
* Converts a number from an arbitrary base.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for base conversion.
*
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base.
* @param int $base The base of the number, validated from 2 to 36.
*
* @return string The converted number, following the Calculator conventions.
*/
public function fromBase(string $number, int $base) : string {
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base);
}
/**
* Converts a number to an arbitrary base.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for base conversion.
*
* @param string $number The number to convert, following the Calculator conventions.
* @param int $base The base to convert to, validated from 2 to 36.
*
* @return string The converted number, lowercase.
*/
public function toBase(string $number, int $base) : string {
$negative = $number[0] === '-';
if ($negative) {
$number = \substr($number, 1);
}
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
if ($negative) {
return '-' . $number;
}
return $number;
}
/**
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10.
*
* @param string $number The number to convert, validated as a non-empty string,
* containing only chars in the given alphabet/base.
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
* @param int $base The base of the number, validated from 2 to alphabet length.
*
* @return string The number in base 10, following the Calculator conventions.
*/
public final function fromArbitraryBase(string $number, string $alphabet, int $base) : string {
// remove leading "zeros"
$number = \ltrim($number, $alphabet[0]);
if ($number === '') {
return '0';
}
// optimize for "one"
if ($number === $alphabet[1]) {
return '1';
}
$result = '0';
$power = '1';
$base = (string) $base;
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
$index = \strpos($alphabet, $number[$i]);
if ($index !== 0) {
$result = $this->add($result, $index === 1 ? $power : $this->mul($power, (string) $index));
}
if ($i !== 0) {
$power = $this->mul($power, $base);
}
}
return $result;
}
/**
* Converts a non-negative number to an arbitrary base using a custom alphabet.
*
* @param string $number The number to convert, positive or zero, following the Calculator conventions.
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
* @param int $base The base to convert to, validated from 2 to alphabet length.
*
* @return string The converted number in the given alphabet.
*/
public final function toArbitraryBase(string $number, string $alphabet, int $base) : string {
if ($number === '0') {
return $alphabet[0];
}
$base = (string) $base;
$result = '';
while ($number !== '0') {
[
$number,
$remainder,
] = $this->divQR($number, $base);
$remainder = (int) $remainder;
$result .= $alphabet[$remainder];
}
return \strrev($result);
}
/**
* Performs a rounded division.
*
* Rounding is performed when the remainder of the division is not zero.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
* @param RoundingMode $roundingMode The rounding mode.
*
* @throws \InvalidArgumentException If the rounding mode is invalid.
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
*
* @psalm-suppress ImpureFunctionCall
*/
public final function divRound(string $a, string $b, RoundingMode $roundingMode) : string {
[
$quotient,
$remainder,
] = $this->divQR($a, $b);
$hasDiscardedFraction = $remainder !== '0';
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
$discardedFractionSign = function () use ($remainder, $b) : int {
$r = $this->abs($this->mul($remainder, '2'));
$b = $this->abs($b);
return $this->cmp($r, $b);
};
$increment = false;
switch ($roundingMode) {
case RoundingMode::UNNECESSARY:
if ($hasDiscardedFraction) {
throw RoundingNecessaryException::roundingNecessary();
}
break;
case RoundingMode::UP:
$increment = $hasDiscardedFraction;
break;
case RoundingMode::DOWN:
break;
case RoundingMode::CEILING:
$increment = $hasDiscardedFraction && $isPositiveOrZero;
break;
case RoundingMode::FLOOR:
$increment = $hasDiscardedFraction && !$isPositiveOrZero;
break;
case RoundingMode::HALF_UP:
$increment = $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_DOWN:
$increment = $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_CEILING:
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_FLOOR:
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_EVEN:
$lastDigit = (int) $quotient[-1];
$lastDigitIsEven = $lastDigit % 2 === 0;
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
default:
throw new \InvalidArgumentException('Invalid rounding mode.');
}
if ($increment) {
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1');
}
return $quotient;
}
/**
* Calculates bitwise AND of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*/
public function and(string $a, string $b) : string {
return $this->bitwise('and', $a, $b);
}
/**
* Calculates bitwise OR of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*/
public function or(string $a, string $b) : string {
return $this->bitwise('or', $a, $b);
}
/**
* Calculates bitwise XOR of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*/
public function xor(string $a, string $b) : string {
return $this->bitwise('xor', $a, $b);
}
/**
* Performs a bitwise operation on a decimal number.
*
* @param 'and'|'or'|'xor' $operator The operator to use.
* @param string $a The left operand.
* @param string $b The right operand.
*/
private function bitwise(string $operator, string $a, string $b) : string {
[
$aNeg,
$bNeg,
$aDig,
$bDig,
] = $this->init($a, $b);
$aBin = $this->toBinary($aDig);
$bBin = $this->toBinary($bDig);
$aLen = \strlen($aBin);
$bLen = \strlen($bBin);
if ($aLen > $bLen) {
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin;
}
elseif ($bLen > $aLen) {
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin;
}
if ($aNeg) {
$aBin = $this->twosComplement($aBin);
}
if ($bNeg) {
$bBin = $this->twosComplement($bBin);
}
$value = match ($operator) { 'and' => $aBin & $bBin,
'or' => $aBin | $bBin,
'xor' => $aBin ^ $bBin,
};
$negative = match ($operator) { 'and' => $aNeg and $bNeg,
'or' => $aNeg or $bNeg,
'xor' => $aNeg xor $bNeg,
};
if ($negative) {
$value = $this->twosComplement($value);
}
$result = $this->toDecimal($value);
return $negative ? $this->neg($result) : $result;
}
/**
* @param string $number A positive, binary number.
*/
private function twosComplement(string $number) : string {
$xor = \str_repeat("\xff", \strlen($number));
$number ^= $xor;
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
$byte = \ord($number[$i]);
if (++$byte !== 256) {
$number[$i] = \chr($byte);
break;
}
$number[$i] = "\x00";
if ($i === 0) {
$number = "\x01" . $number;
}
}
return $number;
}
/**
* Converts a decimal number to a binary string.
*
* @param string $number The number to convert, positive or zero, only digits.
*/
private function toBinary(string $number) : string {
$result = '';
while ($number !== '0') {
[
$number,
$remainder,
] = $this->divQR($number, '256');
$result .= \chr((int) $remainder);
}
return \strrev($result);
}
/**
* Returns the positive decimal representation of a binary number.
*
* @param string $bytes The bytes representing the number.
*/
private function toDecimal(string $bytes) : string {
$result = '0';
$power = '1';
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) {
$index = \ord($bytes[$i]);
if ($index !== 0) {
$result = $this->add($result, $index === 1 ? $power : $this->mul($power, (string) $index));
}
if ($i !== 0) {
$power = $this->mul($power, '256');
}
}
return $result;
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overrides |
---|---|---|---|---|
Calculator::$instance | private static | property | The Calculator instance in use. | |
Calculator::abs | final public | function | Returns the absolute value of a number. | |
Calculator::add | abstract public | function | Adds two numbers. | 3 |
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::divQ | abstract public | function | Returns the quotient of the division of two numbers. | 3 |
Calculator::divQR | abstract public | function | Returns the quotient and remainder of the division of two numbers. | 3 |
Calculator::divR | abstract public | function | Returns the remainder of the division of two numbers. | 3 |
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::modPow | abstract public | function | Raises a number into power with modulo. | 3 |
Calculator::mul | abstract public | function | Multiplies two numbers. | 3 |
Calculator::neg | final public | function | Negates a number. | |
Calculator::or | public | function | Calculates bitwise OR of two numbers. | 1 |
Calculator::pow | abstract public | function | Exponentiates a number. | 3 |
Calculator::set | final public static | function | Sets the Calculator instance to use. | |
Calculator::sqrt | abstract public | function | Returns the square root of the given number, rounded down. | 3 |
Calculator::sub | abstract public | function | Subtracts two numbers. | 3 |
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 |