Skip to content

Commit f7a9038

Browse files
committed
Update changelog
1 parent 26be635 commit f7a9038

5 files changed

Lines changed: 33 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## Money v5.24.0
66

7-
This is the changelog for Money v5.23.1 released on January 17th, 2026. For older changelogs please consult the release tag on [GitHub](https://github.com/kipcole9/money/tags)
7+
This is the changelog for Money v5.24.0 released on January 18th, 2026. For older changelogs please consult the release tag on [GitHub](https://github.com/kipcole9/money/tags)
88

99
### Bug Fixes
1010

@@ -14,7 +14,9 @@ This is the changelog for Money v5.23.1 released on January 17th, 2026. For old
1414

1515
### Enhancements
1616

17-
* Add support for custom currency codes.
17+
* Add `Money.spread/3` to proptionally allocate (that is, spread) am money amount amongst a list of amounts or integers. Thanks to @jdewar for the PR and great patience. Closes #175.
18+
19+
* Add support for custom currency codes. A custom currency code is created with `Cldr.Currency.new/2` and is distinguised from an ISO4217 code by being at least 4 characters long (ISO4217 codes are always 3 characters long). The first character must be a Latin alphabetic character. The additional characters must be Latin alphanumeric characters.
1820

1921
## Money v5.23.0
2022

lib/money.ex

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,66 +2058,68 @@ defmodule Money do
20582058
20592059
## Arguments
20602060
2061-
* `amount` is any `t:Money.t/0`
2061+
* `amount` is any `t:Money.t/0`.
20622062
20632063
* `portions` may be a list of `t:Money.t/0`, a list of numbers, or an integer
2064-
into which the `money` is spread
2064+
into which the `money` is spread.
20652065
2066-
* `opts` is a keyword list of options, as defined by `Money.round/2`
2066+
* `options` is a keyword list of options, which will be applied to
2067+
`Money.round/2`.
20672068
2068-
Returns a %Money{} list the same length (or value of the integer), with the amount spread
2069-
as evenly as the currency's smallest unit allows. The result is derived as follows:
2069+
Returns a list of `t:Money.t/0` that is the same length (or value of the integer),
2070+
with the amount spread as evenly as the currency's smallest unit allows.
2071+
The result is derived as follows:
20702072
2071-
1. Round the amount to the currency's default precision
2073+
1. Round the amount to the currency's default precision.
20722074
2073-
2. Calculate partial sums of the given portions
2075+
2. Calculate partial sums of the given portions.
20742076
20752077
3. Starting with the last portion, calculate the expected remaining amount then
20762078
subtract and round that portion's value from the current remaining amount.
20772079
2078-
eg. with [2, 1] as portions and $1 to spread, we calculate that 2/3 of the amount
2079-
should remain after `1` receives its portion, so we subtract the unrounded Money amount of
2080-
0.666666, and we round the share to $0.33. Then $1.00 - 0.33 is the new remaining amount.
2081-
This approach avoids numerical instability by using the expected remaining amount,
2082-
rather than summing up values as they are doled out.
2080+
For example, with `[2, 1]` as portions and `$1` to spread, we calculate that 2/3 of the amount
2081+
should remain after `1` receives its portion, so we subtract the unrounded Money amount of
2082+
`0.666666`, and we round the share to `$0.33`. Then `$1.00 - 0.33` is the new remaining amount.
2083+
This approach avoids numerical instability by using the expected remaining amount,
2084+
rather than summing up values as they are doled out.
20832085
20842086
## Examples
20852087
2086-
iex> Money.spread([Money.new(:usd, 10), Money.new(:usd, 1)], Money.new(:usd, 10))
2088+
iex> Money.spread(Money.new(:usd, 10), [Money.new(:usd, 10), Money.new(:usd, 1)])
20872089
[Money.new(:USD, "9.09"), Money.new(:USD, "0.91")]
20882090
2089-
iex> Money.spread([2.5, 1, 1], Money.new(:usd, "2.50"))
2091+
iex> Money.spread(Money.new(:usd, "2.50"), [2.5, 1, 1])
20902092
[Money.new(:USD, "1.39"), Money.new(:USD, "0.55"), Money.new(:USD, "0.56")]
20912093
2092-
iex> Money.spread(3, Money.new(:usd, 2))
2094+
iex> Money.spread(Money.new(:usd, 2), 3)
20932095
[Money.new(:USD, "0.67"), Money.new(:USD, "0.66"), Money.new(:USD, "0.67")]
20942096
20952097
"""
2096-
@spec spread(list(Money.t()) | list(number()) | integer(), Money.t()) :: list(Money.t())
2097-
def spread(portions, amount, opts \\ [])
2098+
@spec spread(Money.t(), list(Money.t()) | list(number()) | integer()) :: list(Money.t())
2099+
def spread(amount, portions, options \\ [])
20982100
def spread([], _, _), do: []
20992101

2100-
def spread(portions, amount, opts) when is_integer(portions) do
2101-
spread(List.duplicate(1, portions), amount, opts)
2102+
def spread(amount, portions, options) when is_integer(portions) do
2103+
spread(amount, List.duplicate(1, portions), options)
21022104
end
21032105

2104-
def spread([h | _] = portions, %Money{} = amount, opts) do
2105-
{shares, _, _} = recurse_spread(portions, spread_zero(h), round(amount), opts)
2106+
def spread(%Money{} = amount, [h | _] = portions, options) do
2107+
{shares, _, _} = recurse_spread(portions, spread_zero(h), round(amount), options)
21062108
shares
21072109
end
21082110

21092111
def spread(_, _, _), do: raise("Amount to spread must be Money.t()")
21102112

21112113
defp recurse_spread([], total, amount, _opts), do: {[], amount, total}
21122114

2113-
defp recurse_spread([head | tail], curr_sum, amount, opts) do
2115+
defp recurse_spread([head | tail], curr_sum, amount, options) do
21142116
partial_sum = spread_sum(head, curr_sum)
2115-
{shares, remaining, total} = recurse_spread(tail, partial_sum, amount, opts)
2117+
{shares, remaining, total} = recurse_spread(tail, partial_sum, amount, options)
21162118

21172119
proportion_remaining = prop_remaining(curr_sum, total)
21182120
unrounded_now_remaining = mult!(amount, proportion_remaining)
21192121

2120-
share = sub!(remaining, unrounded_now_remaining) |> round(opts)
2122+
share = sub!(remaining, unrounded_now_remaining) |> round(options)
21212123
now_remaining = sub!(remaining, share)
21222124

21232125
{[share | shares], now_remaining, total}

test/spread_integer_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule MoneySpreadIntegerTest do
99
max_runs: 1_000
1010
) do
1111
amount = Money.from_integer(spread_pennies, :usd)
12-
splits = Money.spread(portions, amount)
12+
splits = Money.spread(amount, portions)
1313

1414
{:ok, sum} = Money.sum(splits)
1515
assert Money.equal?(sum, amount)

test/spread_money_structs_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defmodule MoneySpreadMoneyStructsTest do
1212
amount = Money.from_integer(spread_pennies, code)
1313
portions = Enum.map(portions, &Money.from_integer(&1, code))
1414

15-
splits = Money.spread(portions, amount)
15+
splits = Money.spread(amount, portions)
1616

1717
{:ok, sum} = Money.sum(splits)
1818
assert Money.equal?(sum, amount)

test/spread_numbers_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule MoneySpreadNumbersTest do
1515
max_runs: 1_000
1616
) do
1717
amount = Money.from_integer(spread_pennies, :usd)
18-
splits = Money.spread(portions, amount)
18+
splits = Money.spread(amount, portions)
1919

2020
{:ok, sum} = Money.sum(splits)
2121
assert Money.equal?(sum, amount)

0 commit comments

Comments
 (0)