* @license https://opensource.org/licenses/MIT MIT License */ namespace PrestaShop\Decimal\Operation; use PrestaShop\Decimal\DecimalNumber; /** * Computes the multiplication between two decimal numbers */ class Multiplication { /** * Performs the multiplication * * @param DecimalNumber $a Left operand * @param DecimalNumber $b Right operand * * @return DecimalNumber Result of the multiplication */ public function compute(DecimalNumber $a, DecimalNumber $b) { if (function_exists('bcmul')) { return $this->computeUsingBcMath($a, $b); } return $this->computeWithoutBcMath($a, $b); } /** * Performs the multiplication using BC Math * * @param DecimalNumber $a Left operand * @param DecimalNumber $b Right operand * * @return DecimalNumber Result of the multiplication */ public function computeUsingBcMath(DecimalNumber $a, DecimalNumber $b) { $precision1 = $a->getPrecision(); $precision2 = $b->getPrecision(); return new DecimalNumber((string) bcmul($a, $b, $precision1 + $precision2)); } /** * Performs the multiplication without BC Math * * @param DecimalNumber $a Left operand * @param DecimalNumber $b Right operand * * @return DecimalNumber Result of the multiplication */ public function computeWithoutBcMath(DecimalNumber $a, DecimalNumber $b) { $aAsString = (string) $a; $bAsString = (string) $b; // optimization: if either one is zero, the result is zero if ('0' === $aAsString || '0' === $bAsString) { return new DecimalNumber('0'); } // optimization: if either one is one, the result is the other one if ('1' === $aAsString) { return $b; } if ('1' === $bAsString) { return $a; } $result = $this->multiplyStrings( ltrim($a->getCoefficient(), '0'), ltrim($b->getCoefficient(), '0') ); $sign = ($a->isNegative() xor $b->isNegative()) ? '-' : ''; // a multiplication has at most as many decimal figures as the sum // of the number of decimal figures the factors have $exponent = $a->getExponent() + $b->getExponent(); return new DecimalNumber($sign . $result, $exponent); } /** * Multiplies two integer numbers as strings. * * This method implements a naive "long multiplication" algorithm. * * @param string $topNumber * @param string $bottomNumber * * @return string */ private function multiplyStrings($topNumber, $bottomNumber) { $topNumberLength = strlen($topNumber); $bottomNumberLength = strlen($bottomNumber); if ($topNumberLength < $bottomNumberLength) { // multiplication is commutative, and this algorithm // performs better if the bottom number is shorter. return $this->multiplyStrings($bottomNumber, $topNumber); } $stepNumber = 0; $result = new DecimalNumber('0'); for ($i = $bottomNumberLength - 1; $i >= 0; --$i) { $carryOver = 0; $partialResult = ''; // optimization: we don't need to bother multiplying by zero if ($bottomNumber[$i] === '0') { ++$stepNumber; continue; } if ($bottomNumber[$i] === '1') { // multiplying by one is the same as copying the top number $partialResult = strrev($topNumber); } else { // digit-by-digit multiplication using carry-over for ($j = $topNumberLength - 1; $j >= 0; --$j) { $multiplicationResult = ($bottomNumber[$i] * $topNumber[$j]) + $carryOver; $carryOver = floor($multiplicationResult / 10); $partialResult .= $multiplicationResult % 10; } if ($carryOver > 0) { $partialResult .= $carryOver; } } // pad the partial result with as many zeros as performed steps $padding = str_pad('', $stepNumber, '0'); $partialResult = $padding . $partialResult; // add to the result $result = $result->plus( new DecimalNumber(strrev($partialResult)) ); ++$stepNumber; } return (string) $result; } }