mirror of
https://github.com/danog/math.git
synced 2025-01-22 13:41:12 +01:00
Exact division algorithm is now in BigDecimal::dividedBy()
BigRational::toBigDecimal() is now really just a proxy method to BigDecimal::dividedBy().
This commit is contained in:
parent
5ccc6641b5
commit
1420202440
@ -150,7 +150,31 @@ final class BigDecimal extends BigNumber implements \Serializable
|
|||||||
{
|
{
|
||||||
$that = BigDecimal::of($that);
|
$that = BigDecimal::of($that);
|
||||||
|
|
||||||
$result = $this->toBigRational()->dividedBy($that->toBigRational())->toBigDecimal();
|
if ($that->value === '0') {
|
||||||
|
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->scaleValues($this, $that, $a, $b);
|
||||||
|
|
||||||
|
$d = rtrim($b, '0');
|
||||||
|
$scale = strlen($b) - strlen($d);
|
||||||
|
|
||||||
|
$calculator = Calculator::get();
|
||||||
|
|
||||||
|
foreach ([5, 2] as $prime) {
|
||||||
|
for (;;) {
|
||||||
|
$lastDigit = (int) substr($d, -1);
|
||||||
|
|
||||||
|
if ($lastDigit % $prime !== 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
list ($d) = $calculator->div($d, (string) $prime);
|
||||||
|
$scale++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->dividedToScale($that, $scale)->stripTrailingZeros();
|
||||||
|
|
||||||
if ($result->scale < $this->scale) {
|
if ($result->scale < $this->scale) {
|
||||||
$result = $result->withScale($this->scale);
|
$result = $result->withScale($this->scale);
|
||||||
|
@ -271,37 +271,7 @@ final class BigRational extends BigNumber implements \Serializable
|
|||||||
*/
|
*/
|
||||||
public function toBigDecimal()
|
public function toBigDecimal()
|
||||||
{
|
{
|
||||||
$simplified = $this->simplified();
|
return $this->numerator->toBigDecimal()->dividedBy($this->denominator);
|
||||||
|
|
||||||
$denominator = $simplified->denominator;
|
|
||||||
|
|
||||||
$counts = [
|
|
||||||
2 => 0,
|
|
||||||
5 => 0
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ([2, 5] as $divisor) {
|
|
||||||
do {
|
|
||||||
list ($quotient, $remainder) = $denominator->quotientAndRemainder($divisor);
|
|
||||||
|
|
||||||
if ($remainderIsZero = $remainder->isZero()) {
|
|
||||||
$denominator = $quotient;
|
|
||||||
$counts[$divisor]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while ($remainderIsZero);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $denominator->isEqualTo(1)) {
|
|
||||||
throw new RoundingNecessaryException('This rational number cannot be represented as a finite decimal number without rounding.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$maxDecimalPlaces = max($counts[2], $counts[5]);
|
|
||||||
|
|
||||||
return $simplified->numerator
|
|
||||||
->toBigDecimal()
|
|
||||||
->dividedToScale($simplified->denominator, $maxDecimalPlaces)
|
|
||||||
->stripTrailingZeros();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Brick\Math\Tests;
|
namespace Brick\Math\Tests;
|
||||||
|
|
||||||
use Brick\Math\BigDecimal;
|
use Brick\Math\BigDecimal;
|
||||||
|
use Brick\Math\Exception\DivisionByZeroException;
|
||||||
use Brick\Math\RoundingMode;
|
use Brick\Math\RoundingMode;
|
||||||
use Brick\Math\Exception\RoundingNecessaryException;
|
use Brick\Math\Exception\RoundingNecessaryException;
|
||||||
|
|
||||||
@ -633,17 +634,20 @@ class BigDecimalTest extends AbstractTestCase
|
|||||||
*
|
*
|
||||||
* @param string|number $number The number to divide.
|
* @param string|number $number The number to divide.
|
||||||
* @param string|number $divisor The divisor.
|
* @param string|number $divisor The divisor.
|
||||||
* @param string|null $expected The expected result, or null if an exception is expected.
|
* @param string $expected The expected result, or a class name if an exception is expected.
|
||||||
*/
|
*/
|
||||||
public function testDividedBy($number, $divisor, $expected)
|
public function testDividedBy($number, $divisor, $expected)
|
||||||
{
|
{
|
||||||
if ($expected === null) {
|
$number = BigDecimal::of($number);
|
||||||
$this->setExpectedException(RoundingNecessaryException::class);
|
|
||||||
|
if ($this->isException($expected)) {
|
||||||
|
$this->setExpectedException($expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
$actual = BigDecimal::of($number)->dividedBy($divisor);
|
$actual = $number->dividedBy($divisor);
|
||||||
|
|
||||||
if ($expected !== null) {
|
if (! $this->isException($expected)) {
|
||||||
|
$this->assertInstanceOf(BigDecimal::class, $actual);
|
||||||
$this->assertSame($expected, (string) $actual);
|
$this->assertSame($expected, (string) $actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -657,13 +661,13 @@ class BigDecimalTest extends AbstractTestCase
|
|||||||
[1, 1, '1'],
|
[1, 1, '1'],
|
||||||
['1.0', '1.00', '1.0'],
|
['1.0', '1.00', '1.0'],
|
||||||
[1, 2, '0.5'],
|
[1, 2, '0.5'],
|
||||||
[1, 3, null],
|
[1, 3, RoundingNecessaryException::class],
|
||||||
[1, 4, '0.25'],
|
[1, 4, '0.25'],
|
||||||
[1, 5, '0.2'],
|
[1, 5, '0.2'],
|
||||||
[1, 6, null],
|
[1, 6, RoundingNecessaryException::class],
|
||||||
[1, 7, null],
|
[1, 7, RoundingNecessaryException::class],
|
||||||
[1, 8, '0.125'],
|
[1, 8, '0.125'],
|
||||||
[1, 9, null],
|
[1, 9, RoundingNecessaryException::class],
|
||||||
[1, 10, '0.1'],
|
[1, 10, '0.1'],
|
||||||
['1.0', 2, '0.5'],
|
['1.0', 2, '0.5'],
|
||||||
['1.00', 2, '0.50'],
|
['1.00', 2, '0.50'],
|
||||||
@ -675,6 +679,8 @@ class BigDecimalTest extends AbstractTestCase
|
|||||||
['1234.5678', '4', '308.64195'],
|
['1234.5678', '4', '308.64195'],
|
||||||
['1234.5678', '8', '154.320975'],
|
['1234.5678', '8', '154.320975'],
|
||||||
['1234.5678', '6.4', '192.90121875'],
|
['1234.5678', '6.4', '192.90121875'],
|
||||||
|
['123', '0', DivisionByZeroException::class],
|
||||||
|
[-789, '0.0', DivisionByZeroException::class],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user