From 5533b2aa36ae6691f5b81c1e1ec357d5c8b35d9c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 12 Jun 2015 12:03:10 +0200 Subject: [PATCH] Move rounding code from Calculator to BigDecimal --- src/BigDecimal.php | 70 +++++++++++++++++++++++++++++-- src/Internal/Calculator.php | 82 ------------------------------------- 2 files changed, 67 insertions(+), 85 deletions(-) diff --git a/src/BigDecimal.php b/src/BigDecimal.php index b4d12a6..b403db9 100644 --- a/src/BigDecimal.php +++ b/src/BigDecimal.php @@ -264,6 +264,9 @@ public function multipliedBy($that) * @param int|null $scale The desired scale, or null to use the scale of this number. * * @return BigDecimal + * + * @throws ArithmeticException If RoundingMode::UNNECESSARY is provided and rounding was necessary. + * @throws \InvalidArgumentException If any of the arguments is not valid. */ public function dividedBy($that, $roundingMode = RoundingMode::UNNECESSARY, $scale = null) { @@ -291,10 +294,71 @@ public function dividedBy($that, $roundingMode = RoundingMode::UNNECESSARY, $sca $q = $that->valueWithMinScale($this->scale - $scale); $calculator = Calculator::get(); - $result = $calculator->divRounded($p, $q, $roundingMode); - if ($result === null) { - throw ArithmeticException::roundingNecessary(); + list ($result, $remainder) = $calculator->div($p, $q); + + $hasDiscardedFraction = ($remainder !== '0'); + $isPositiveOrZero = ($p[0] === '-') === ($q[0] === '-'); + + $discardedFractionSign = function() use ($calculator, $remainder, $q) { + $r = $calculator->abs($calculator->mul($remainder, '2')); + $q = $calculator->abs($q); + + return $calculator->cmp($r, $q); + }; + + $increment = false; + + switch ($roundingMode) { + case RoundingMode::UNNECESSARY: + if ($hasDiscardedFraction) { + throw ArithmeticException::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) substr($result, -1); + $lastDigitIsEven = ($lastDigit % 2 === 0); + $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; + break; + + default: + throw new \InvalidArgumentException('Invalid rounding mode.'); + } + + if ($increment) { + $result = $calculator->add($result, $isPositiveOrZero ? '1' : '-1'); } return new BigDecimal($result, $scale); diff --git a/src/Internal/Calculator.php b/src/Internal/Calculator.php index 3d5b57e..cee06bc 100644 --- a/src/Internal/Calculator.php +++ b/src/Internal/Calculator.php @@ -144,88 +144,6 @@ public function cmp($a, $b) return $this->sign($this->sub($a, $b)); } - /** - * Divides and rounds numbers. - * - * @param string $p The dividend. - * @param string $q The divisor, must not be zero. - * @param integer $roundingMode The rounding mode. - * - * @return string|null The divided and rounded number, or null if rounding was necessary. - * - * @throws \InvalidArgumentException If the rounding mode is invalid. - */ - public function divRounded($p, $q, $roundingMode) - { - list ($result, $remainder) = $this->div($p, $q); - - $hasDiscardedFraction = ($remainder !== '0'); - $isPositiveOrZero = ($p[0] === '-') === ($q[0] === '-'); - - $discardedFractionSign = function() use ($remainder, $q) { - $r = $this->abs($this->mul($remainder, '2')); - $q = $this->abs($q); - - return $this->cmp($r, $q); - }; - - $increment = false; - - switch ($roundingMode) { - case RoundingMode::UNNECESSARY: - if ($hasDiscardedFraction) { - return null; - } - 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) substr($result, -1); - $lastDigitIsEven = ($lastDigit % 2 === 0); - $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; - break; - - default: - throw new \InvalidArgumentException('Invalid rounding mode.'); - } - - if ($increment) { - $result = $this->add($result, $isPositiveOrZero ? '1' : '-1'); - } - - return $result; - } - /** * Adds two numbers. *