Skip to content

Commit

Permalink
Correctly round to cash increment
Browse files Browse the repository at this point in the history
Money.round/2 now correctly uses the rounding
increment for a currency. This is relevant
for currencies like :AUD and :CHF which have
minimum cash rounding of 0.05 even though
the accounting increment is 0.01.

The documentation now correctly names the
option to Money.round/2 as :currency_digits
which may be set to :cash, :accounting or :iso
with a default of :iso

Closes #56
  • Loading branch information
kipcole9 committed Feb 21, 2018
1 parent ea88e6e commit 56a4b15
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 18 deletions.
49 changes: 32 additions & 17 deletions lib/money.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,7 @@ defmodule Money do
end

@doc """
Round a `Money` value into the acceptable range for the defined currency.
Round a `Money` value into the acceptable range for the requested currency.
## Options
Expand All @@ -1041,25 +1041,27 @@ defmodule Money do
`Decimal.Context`. The default is `:half_even` which is also known
as "banker's rounding"
* `:cash` which determines whether the rounding is being applied to
an accounting amount or a cash amount. Some currencies, such as the
:AUD and :CHF have a cash unit increment minimum which requires
a different rounding increment to an arbitrary accounting amount. The
default is `false`.
* `:currency_digits` which determines the rounding increment.
The valid options are `:cash`, `:accounting` and `:iso`. The
default is `:iso`. The rounding increment applies to currencies
such as :AUD and :CHF which have an accounting increment of 0.01
but a minimum cash increment of 0.05.
## Notes
There are two kinds of rounding applied:
1. Round to the appropriate number of fractional digits
1. Round to the appropriate number of fractional digits
2. Apply an appropriate rounding increment. Most currencies
round to the same precision as the number of decimal digits, but some
such as :AUD and :CHF round to a minimum such as 0.05 when its a cash
amount.
round to the same precision as the number of decimal digits, but some
such as :AUD and :CHF round to a minimum such as 0.05 when its a cash
amount.
## Examples
iex> Money.round Money.new("123.7456", :CHF), cash: true
#Money<:CHF, 125>
iex> Money.round Money.new("123.73", :CHF), currency_digits: :cash
#Money<:CHF, 123.75>
iex> Money.round Money.new("123.7456", :CHF)
#Money<:CHF, 123.75>
Expand Down Expand Up @@ -1106,18 +1108,23 @@ defmodule Money do

defp round_to_nearest(%Money{currency: code} = money, opts) do
with {:ok, currency} <- Currency.currency_for_code(code) do
increment = if opts[:cash], do: currency.cash_rounding, else: currency.rounding
do_round_to_nearest(money, increment, opts)
digits = digits_from_opts(currency, opts[:currency_digits])
increment = increment_from_opts(currency, opts[:currency_digits])
do_round_to_nearest(money, digits, increment, opts)
end
end

defp do_round_to_nearest(money, 0, _opts) do
defp do_round_to_nearest(money, _digits, 0, _opts) do
money
end

defp do_round_to_nearest(money, increment, opts) do
defp do_round_to_nearest(money, digits, increment, opts) do
rounding_mode = Keyword.get(opts, :rounding_mode, @default_rounding_mode)
rounding = Decimal.new(increment)
rounding =
-digits
|> Cldr.Math.power_of_10
|> Kernel.*(increment)
|> Decimal.new

rounded_amount =
money.amount
Expand All @@ -1128,6 +1135,14 @@ defmodule Money do
%Money{currency: money.currency, amount: rounded_amount}
end

defp increment_from_opts(currency, :cash) do
currency.cash_rounding
end

defp increment_from_opts(currency, _) do
currency.rounding
end

@doc """
Convert `money` from one currency to another.
Expand Down
7 changes: 6 additions & 1 deletion test/money_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,12 @@ defmodule MoneyTest do
end

test "Money is rounded according to currency cash definition for CHF" do
assert Money.round(Money.new(:CHF, "123.456"), cash: true) == Money.new(:CHF, 125)
assert Money.round(Money.new(:CHF, "123.456"), currency_digits: :cash) ==
Money.new(:CHF, "123.45")
assert Money.round(Money.new(:CHF, "123.41"), currency_digits: :cash) ==
Money.new(:CHF, "123.40")
assert Money.round(Money.new(:CHF, "123.436"), currency_digits: :cash) ==
Money.new(:CHF, "123.45")
end

test "Extract decimal from money" do
Expand Down

0 comments on commit 56a4b15

Please sign in to comment.