From 3935629c1572923568109e3b400e821889b000af Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sat, 20 Apr 2019 00:19:06 +0200 Subject: [PATCH] Improve NativeCalculator::doAdd() performance The additions are now performed in blocks of n digits (n depending on int size on target platform) instead of digit-by-digit as before. --- src/Internal/Calculator/NativeCalculator.php | 43 ++++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/Internal/Calculator/NativeCalculator.php b/src/Internal/Calculator/NativeCalculator.php index 1720621..f6c1df4 100644 --- a/src/Internal/Calculator/NativeCalculator.php +++ b/src/Internal/Calculator/NativeCalculator.php @@ -17,9 +17,13 @@ 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. * + * For 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) + * * @var int */ - private $maxDigits = 0; + private $maxDigits; /** * Class constructor. @@ -36,6 +40,9 @@ public function __construct() case 8: $this->maxDigits = 18; break; + + default: + throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'); } } @@ -253,24 +260,42 @@ private function doAdd(string $a, string $b, int $x, int $y) : string $carry = 0; $result = ''; - for ($i = $length - 1; $i >= 0; $i--) { - $sum = (int) $a[$i] + (int) $b[$i] + $carry; + for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { + $blockLength = $this->maxDigits; + + if ($i < 0) { + $blockLength += $i; + $i = 0; + } + + $blockA = \substr($a, $i, $blockLength); + $blockB = \substr($b, $i, $blockLength); + + $sum = (string) ((int) $blockA + (int) $blockB + $carry); + $sumLength = \strlen($sum); - if ($sum >= 10) { + if ($sumLength > $blockLength) { + $sum = \substr($sum, 1); $carry = 1; - $sum -= 10; } else { + if ($sumLength < $blockLength) { + $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; + } $carry = 0; } - $result .= $sum; + $result = $sum . $result; + + if ($i === 0) { + break; + } } - if ($carry !== 0) { - $result .= $carry; + if ($carry === 1) { + $result = '1' . $result; } - return \strrev($result); + return $result; } /**