/** * Performs a rounded division. * * Rounding is performed when the remainder of the division is not zero. * * @param string $a The dividend. * @param string $b The divisor. * @param int $roundingMode The rounding mode. * * @return string * * @throws \InvalidArgumentException If the rounding mode is invalid. * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. */ public function divRound($a, $b, $roundingMode) { list($quotient, $remainder) = $this->divQR($a, $b); $hasDiscardedFraction = $remainder !== '0'; $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); $discardedFractionSign = function () use($remainder, $b) { $r = $this->abs($this->mul($remainder, '2')); $b = $this->abs($b); return $this->cmp($r, $b); }; $increment = false; switch ($roundingMode) { case RoundingMode::UNNECESSARY: if ($hasDiscardedFraction) { throw RoundingNecessaryException::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($quotient, -1); $lastDigitIsEven = $lastDigit % 2 === 0; $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; default: throw new \InvalidArgumentException('Invalid rounding mode.'); } if ($increment) { return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); } return $quotient; }
/** * @param int $roundingMode The rounding mode. * @param BigDecimal $number The number to round. * @param string $divisor The divisor. * @param string|null $two The expected rounding to a scale of two, or null if an exception is expected. * @param string|null $one The expected rounding to a scale of one, or null if an exception is expected. * @param string|null $zero The expected rounding to a scale of zero, or null if an exception is expected. */ private function doTestRoundingMode($roundingMode, BigDecimal $number, $divisor, $two, $one, $zero) { foreach ([$zero, $one, $two] as $scale => $expected) { if ($expected === null) { $this->setExpectedException(RoundingNecessaryException::getNamespace()); } $actual = $number->dividedBy($divisor, $scale, $roundingMode); if ($expected !== null) { $this->assertBigDecimalInternalValues($expected, $scale, $actual); } } }
/** * @param integer $roundingMode The rounding mode. * @param BigInteger $number The number to round. * @param string $divisor The divisor. * @param string|null $ten The expected rounding to a scale of two, or null if an exception is expected. * @param string|null $hundred The expected rounding to a scale of one, or null if an exception is expected. * @param string|null $thousand The expected rounding to a scale of zero, or null if an exception is expected. */ private function doTestDividedByWithRoundingMode($roundingMode, BigInteger $number, $divisor, $ten, $hundred, $thousand) { foreach ([$ten, $hundred, $thousand] as $expected) { $divisor .= '0'; if ($expected === null) { $this->setExpectedException(RoundingNecessaryException::getNamespace()); } $actual = $number->dividedBy($divisor, $roundingMode); if ($expected !== null) { $this->assertBigIntegerEquals($expected, $actual); } } }
/** * @return array */ public function providerToScale() { return [['1/8', 3, RoundingMode::UNNECESSARY, '0.125'], ['1/16', 3, RoundingMode::UNNECESSARY, RoundingNecessaryException::getNamespace()], ['1/16', 3, RoundingMode::HALF_DOWN, '0.062'], ['1/16', 3, RoundingMode::HALF_UP, '0.063'], ['1/9', 30, RoundingMode::DOWN, '0.111111111111111111111111111111'], ['1/9', 30, RoundingMode::UP, '0.111111111111111111111111111112'], ['1/9', 100, RoundingMode::UNNECESSARY, RoundingNecessaryException::getNamespace()]]; }