Closed nickdichev closed 4 years ago
Thanks for the question, I will update the docs to explain better. For now I hope this helps, definitely keep asking questions if you have any.
The precision of the currency is defined in CLDR and with few exceptions is the same as the precision defined by ISO4217. There are some cases where CLDR and ISO disagree. The disagreement is usually where common practise in a country is different from the formal definition. Therefore in typical usage CLDR is to be preferred (ie use currency.digits
). Where you know that you need the formal standard, use currency.iso_digits
.
You can find which currencies have digits
different from iso_digits
with the following snippet:
iex> Cldr.Currency.currencies_for_locale!("en", MyApp.Cldr)
... |> Enum.filter(fn {k, v} -> v.digits != v.iso_digits && !is_nil(v.iso_digits) end)
One example is the Lebanese Pound where you can see that digits
is 0
suggesting that in common use, decimal digits are not used. iso_digits
is 2
noting that the standard has a precision of two digits.
%Cldr.Currency{
alt_code: nil,
cash_digits: 0,
cash_rounding: 0,
code: "LBP",
count: %{one: "Lebanese pound", other: "Lebanese pounds"},
digits: 0,
from: nil,
iso_digits: 2,
name: "Lebanese Pound",
narrow_symbol: "L£",
rounding: 0,
symbol: "LBP",
tender: true,
to: nil
}
Some currencies apply "round to nearest". Canadian Dollar and Swiss Franc are two well known examples. Both of these currencies, in their cash form, have no coin or note below 5 cents
or 5 centimes
. Therefore when dealing with cash amounts, they must always be rounded to the cash_rounding
amount. The following snippet will tell you which currencies this applies to:
iex> Cldr.Currency.currencies_for_locale!("en", MyApp.Cldr)
... |> Enum.filter(fn {k, v} -> v.cash_rounding > 0 end)
Same intent as currency.cash_rounding
but applied to non-cash amounts. Today there are no currencies which apply rounding in this way.
ex_money
manages all of this for you. Just call Money.round/2
and it will take care of rounding and has options to decide which digits to use and what usage. Using your requirement for JPY
for example:
iex> Money.round Money.new("123.7456", :JPY)
#Money<:JPY, 124>
Money.to_string/2
will also round prior to formatting and will also format the appropriate number of decimal digits.
Awesome! Thank you so much, that makes a lot of sense. Thanks again for all your hard work on your libraries; we're you're biggest fans at Peek!
Thanks for the feedback, it definitely helps with motivation :-)
Just one more note on rounding if you are doing the rounding yourself:
First round to the number of digits. Use :half_even
rounding unless you have some specific requirement. This is the standard rounding method for financial transactions.
Then round to the nearest if required (ie cash_rounding > 0
)
Rounding should only be performed on output. Precision should be preserved during all mathematical transformations. Round only at the last minute when presenting a money amount to a user, or when required prior to an API call.
Hi, first of all thank you for the wonderful library!
I was hoping that you could give a brief explanation of the
%Cldr.Currency{}
fields, in particular,cash_digits
,cash_rounding
,digits
,iso_digits
androunding
.For some background, we have an in-house library to calculate payment distribution for our ecommerce application. Previously, the library was hardcoded to always round to two decimal places since we only supported these types of currencies. However, now we are adding support for JPY into our application. I made changes to the payment library to accept an option with the number of decimal places should be used for rounding. However, I am a bit confused which field should be used to grab the correct value in the application before calling out to the library.
By just looking at the output of
Money.Currency.currency_for_code/1
for"JPY"
or"USD"
for our use case it has to be eithercash_digits
,digits
oriso_digits
. I'm currently usingiso_digits
to determine the number of decimal places to round with. Is this correct?