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

Breadcrumb

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

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\Calculator
View 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 &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::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

API Navigation

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