Skip to content

Commit

Permalink
Move rounding code from Calculator to BigDecimal
Browse files Browse the repository at this point in the history
  • Loading branch information
BenMorel committed Jun 12, 2015
1 parent cc30889 commit 5533b2a
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 85 deletions.
70 changes: 67 additions & 3 deletions src/BigDecimal.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down
82 changes: 0 additions & 82 deletions src/Internal/Calculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down

0 comments on commit 5533b2a

Please sign in to comment.