Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. Calculator.php

class Calculator

Same name in this branch
  1. 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\Internal
View 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 &amp; 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

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal