Skip to content

Commit

Permalink
Merge 98102b5 into 86cb29c
Browse files Browse the repository at this point in the history
  • Loading branch information
BenMorel committed Aug 1, 2022
2 parents 86cb29c + 98102b5 commit 12f5432
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@
- `AbstractMoney::getAmount()` now has a return type
- `CurrencyConverter`'s constructor does not accept a default `$context` anymore
- `CurrencyConverter::convert()` now requires the `$context` previously accepted by the constructor as third parameter
- `Money::allocateWithRemainder()` now refuses to allocate a portion of the amount that cannot be spread over all ratios, and instead adds that amount to the remainder (#55)

**New ISO currencies**

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -10,7 +10,7 @@
"license": "MIT",
"require": {
"php": "^7.4 || ^8.0",
"brick/math": "~0.7.3 || ~0.8.0 || ~0.9.0 || ~0.10.0"
"brick/math": "~0.10.1"
},
"require-dev": {
"ext-dom": "*",
Expand Down
43 changes: 36 additions & 7 deletions src/Money.php
Expand Up @@ -16,6 +16,7 @@
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Exception\RoundingNecessaryException;
use InvalidArgumentException;

/**
* A monetary value in a given currency. This class is immutable.
Expand Down Expand Up @@ -592,21 +593,49 @@ public function allocateWithRemainder(int ...$ratios) : array
throw new \InvalidArgumentException('Cannot allocateWithRemainder() to zero ratios only.');
}

$monies = [];
$ratios = $this->simplifyRatios(array_values($ratios));
$total = array_sum($ratios);

$remainder = $this;
[, $remainder] = $this->quotientAndRemainder($total);

foreach ($ratios as $ratio) {
$money = $this->multipliedBy($ratio)->quotient($total);
$remainder = $remainder->minus($money);
$monies[] = $money;
}
$toAllocate = $this->minus($remainder);

$monies = array_map(
fn (int $ratio) => $toAllocate->multipliedBy($ratio)->dividedBy($total),
$ratios,
);

$monies[] = $remainder;

return $monies;
}

/**
* @param int[] $ratios
* @psalm-param non-empty-list<int> $ratios
*
* @return int[]
* @psalm-return non-empty-list<int>
*/
private function simplifyRatios(array $ratios): array
{
$gcd = $this->gcdOfMultipleInt($ratios);

return array_map(fn (int $ratio) => intdiv($ratio, $gcd), $ratios);
}

/**
* @param int[] $values
*
* @psalm-param non-empty-list<int> $values
*/
private function gcdOfMultipleInt(array $values): int
{
$values = array_map(fn (int $value) => BigInteger::of($value), $values);

return BigInteger::gcdMultiple(...$values)->toInt();
}

/**
* Splits this Money into a number of parts.
*
Expand Down
10 changes: 7 additions & 3 deletions tests/MoneyTest.php
Expand Up @@ -376,11 +376,13 @@ public function providerAllocate() : array
[['99.99', 'USD'], [100, 100], ['USD 50.00', 'USD 49.99']],
[[100, 'USD'], [30, 20, 40], ['USD 33.34', 'USD 22.22', 'USD 44.44']],
[[100, 'USD'], [30, 20, 40, 40], ['USD 23.08', 'USD 15.39', 'USD 30.77', 'USD 30.76']],
[[100, 'USD'], [30, 20, 40, 0, 40, 0], ['USD 23.08', 'USD 15.39', 'USD 30.77', 'USD 0.00', 'USD 30.76', 'USD 0.00']],
[[100, 'CHF', new CashContext(5)], [1, 2, 3, 7], ['CHF 7.70', 'CHF 15.40', 'CHF 23.10', 'CHF 53.80']],
[['100.123', 'EUR', new AutoContext()], [2, 3, 1, 1], ['EUR 28.607', 'EUR 42.91', 'EUR 14.303', 'EUR 14.303']],
[['0.02', 'EUR'], [1, 1, 1, 1], ['EUR 0.01', 'EUR 0.01', 'EUR 0.00', 'EUR 0.00']],
[['0.02', 'EUR'], [1, 1, 3, 1], ['EUR 0.01', 'EUR 0.00', 'EUR 0.01', 'EUR 0.00']],
[[-100, 'USD'], [30, 20, 40, 40], ['USD -23.08', 'USD -15.39', 'USD -30.77', 'USD -30.76']],
[['0.03', 'GBP'], [75, 25], ['GBP 0.03', 'GBP 0.00']],
];
}

Expand Down Expand Up @@ -431,11 +433,13 @@ public function providerAllocateWithRemainder() : array
[['99.99', 'USD'], [100, 100], ['USD 49.99', 'USD 49.99', 'USD 0.01']],
[[100, 'USD'], [30, 20, 40], ['USD 33.33', 'USD 22.22', 'USD 44.44', 'USD 0.01']],
[[100, 'USD'], [30, 20, 40, 40], ['USD 23.07', 'USD 15.38', 'USD 30.76', 'USD 30.76', 'USD 0.03']],
[[100, 'CHF', new CashContext(5)], [1, 2, 3, 7], ['CHF 7.65', 'CHF 15.35', 'CHF 23.05', 'CHF 53.80', 'CHF 0.15']],
[[100, 'USD'], [30, 20, 40, 0, 0, 40], ['USD 23.07', 'USD 15.38', 'USD 30.76', 'USD 0.00', 'USD 0.00', 'USD 30.76', 'USD 0.03']],
[[100, 'CHF', new CashContext(5)], [1, 2, 3, 7], ['CHF 7.65', 'CHF 15.30', 'CHF 22.95', 'CHF 53.55', 'CHF 0.55']],
[['100.123', 'EUR', new AutoContext()], [2, 3, 1, 1], ['EUR 28.606', 'EUR 42.909', 'EUR 14.303', 'EUR 14.303', 'EUR 0.002']],
[['0.02', 'EUR'], [1, 1, 1, 1], ['EUR 0.00', 'EUR 0.00', 'EUR 0.00', 'EUR 0.00', 'EUR 0.02']],
[['0.02', 'EUR'], [1, 1, 3, 1], ['EUR 0.00', 'EUR 0.00', 'EUR 0.01', 'EUR 0.00', 'EUR 0.01']],
[['0.02', 'EUR'], [1, 1, 3, 1], ['EUR 0.00', 'EUR 0.00', 'EUR 0.00', 'EUR 0.00', 'EUR 0.02']],
[[-100, 'USD'], [30, 20, 40, 40], ['USD -23.07', 'USD -15.38', 'USD -30.76', 'USD -30.76', 'USD -0.03']],
[['0.03', 'GBP'], [75, 25], ['GBP 0.00', 'GBP 0.00', 'GBP 0.03']],
];
}

Expand Down Expand Up @@ -512,7 +516,7 @@ public function providerSplitWithRemainder() : array
[['99.99', 'USD'], 4, ['USD 24.99', 'USD 24.99', 'USD 24.99', 'USD 24.99', 'USD 0.03']],
[[100, 'CHF', new CashContext(5)], 3, ['CHF 33.30', 'CHF 33.30', 'CHF 33.30', 'CHF 0.10']],
[[100, 'CHF', new CashContext(5)], 7, ['CHF 14.25','CHF 14.25', 'CHF 14.25', 'CHF 14.25', 'CHF 14.25', 'CHF 14.25', 'CHF 14.25', 'CHF 0.25']],
[['100.123', 'EUR', new AutoContext()], 4, ['EUR 25.030', 'EUR 25.030', 'EUR 25.030', 'EUR 25.030', 'EUR 0.003']],
[['100.123', 'EUR', new AutoContext()], 4, ['EUR 25.03', 'EUR 25.03', 'EUR 25.03', 'EUR 25.03', 'EUR 0.003']],
[['0.02', 'EUR'], 4, ['EUR 0.00', 'EUR 0.00', 'EUR 0.00', 'EUR 0.00', 'EUR 0.02']],
];
}
Expand Down

0 comments on commit 12f5432

Please sign in to comment.