Sanjibukai
How to handle small amounts/rates/fees when using an integer (e.g. like Money)
Hello everybody,
In order to deal with currencies, we are used to consider the amounts as cents and then using integers.
One can also simply use the Decimal type which will avoid rounding errors of floats.
Another solution can be to use a package that deal with the currency problem.
In elixir there is two main package: money and ex_money
But I have a question that always intrigued me.. And it’s somehow introduced in the 6th falsehood that is documented in this list (from the ex_money doc):
Prices can’t have more precision than the smaller sub-unit of the currency. (counter-example: gas prices)
Indeed, while we are ultimately dealing with at least the cent in our day to day life, we can however face smaller amounts, like tenth of cents in gas prices or some fees (like sending a message that can cost 0.001$)
Let’s take that last example..
If I have to send an email every day, I expect to pay 3 cents at the end of the month (considering 30 days).
But what if I’m using a cumulative value?
The first day, the amount will still be 0 in cents (using an integer)., right?
So if I had to sum each day fee, I’ll never be able to sum the whole charge.
How those kind of little amounts are actually handled?
Edit: I noticed that the money package is using the integer type for the amount part, while ex_money is using a decimal type (to be exact it’s a numeric type in Postgres, but I guess it’s the same).
Does it mean that with ex_money I can store an amount like $0.001 while displaying $0, and still having the correct amount stored in the system?
I mean on day 2 the value will be correctly stored as $0.002 but still displayed $0..
Until the day 10 when the value stored will be $0.010 and hence correctly displayed as $0.01.
Did I correctly understand how the Decimal-like type will behave in ex_money?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 6 of 6 Posts
kip
In
ex_money(I’m the author) this is one reason for using decimal. Rounding to the currencies precision (and for some currencies and usages to a multiple) is deferred until the latest possible moment.Using your example, its perfectly acceptable to have arbitrary precision on money arithmetic. This is relevant not only at the gas pump but in financial trading as well. So maybe I have a value $3.45678. Perfectly OK and in factor in financial instruments a requirement to at least 7 digits if I recall (it may not be that exactly).
Another example is when doing currency conversion - higher precision is desirable.
The at some point the result is credited to a bank account which means applying the appropriate rounding. There are at least three considerations:
Rounding to the right number of decimal digits. 0,1,2, 3 and 4 digits are in use with different currencies around the world.
Use the right rounding mode. Typically you would use bankers rounding which, in the
Decimallibrary is mode:half_evenand is also the default inex_money.Apply any necessary
round nearestfor the currency. For example, in Australia there is no cash amount below 5c so you need to round to the nearest 5c for cash amounts. Similar for the Swiss franc.Last example. Lets say you have $5.23 and you want to split it 3 ways.
Where did the last cent go? Thats the reason I implemented
Money.split/2.Which allows you to decide what you do with the amount left over. Oh, and
Money.split/2takes an arbitrary precision money amount, rounds it correctly and does the math.I don’t recommend using integers for money amounts for all of these reasons.
kip
ex_moneywill allow you to create, store, retrieve and do math on arbitrary precision money amounts.It will correctly round the amount appropriate to the currency whenever you need that value. Calling
Money.to_string/2will apply the appropriate rounding, for example.So yes, in your example, it would be appropriate to have the money amounts be arbitrary in precision and then only round on output.
You will still need to handle the issue of displaying rounded numbers that don’t add exactly to a rounded total (when viewed in a spreadsheet for example. In this case its probably better to round the numbers first and then sum.
Sanjibukai
Thanks for the answer..
I guess that in bank accounts and for bills (when paying by credit card for example) it’s possible to have amounts precise down to the second digit (0.01) right?
And just to be sure.. This rounding is done at will when we want to display a result for example. So besides the rounding the value keeps it’s actual value, right?
Ok, now it makes sense..
In the case of
ex_money, do we need to configure the digit precision (or other kind of settings like the round mode you introduced)? Or is everything handled right out of the box?BTW, I think I’m going to try it right now (using Postgres)..
Sanjibukai
Just a quick question, is it necessary to set both
ex_moneyandex_money_sqlor just the later is ok and it will bring the former? (sorry if it’s obvious).Edit: I just tried by only setting
ex_money_sqland it broughtex_moneyby itself..But what’s the best practice when defining dependencies. Is it better to explicitly declare
ex_moneytoo?I guess that it’s okay to only declare the later since it’s the same for
ectoon Phoenix projects where onlyecto_sqlis used on thedepslist…And just, a quick remark, you might want to bump the version on the docs as well where it’s till v4.0 (same apply to
ex-money_sqlwith v1.0). It’s not a big deal but it can simplify copy/pasting from he doc.Also, how does the custom
sum()aggregate function generated byex_money_sqlperforms relative to the default Postgressumaggregate function?I see that it’s also possible to use
Repo.aggregatewith recent ecto versions.. I guess that it’s just a syntactic sugar and that it’s the same performance as making the query, right?Also, can you give me real quick an example on how to search the db for a given price (maybe in a range too?) and for a given currency (again maybe giving an inclusion list too?). Just to be sure to understand how to deal with the DB..
For example in the example here, what could be the query to order only the USD, or only the AUD, etc.?
kip
Updated the docs for
ex_moneysince it is a major version change. I left theex_money_sqldocs alone since hex will pick up the latest version automatically.Just
ex_money_sqlis enough as you found. Normally better, in my opinion, to let Hex resolve transitive dependencies rather than try to configure them yourself.Its going to be basically the same or very slightly slower since the database does have to check each row it is summing to make sure they are the same currency. I favour correctness in this case over absolute speed.
Yes, its the same under the hood.
Here’s an example (which I’ve added as a test in the repo too):
Basically you have to wrap the composite value component in a
fragmentsince its not natively supported in Ecto since its not cross-db compatible syntax. In this example,currency_codeis the component of the composite fieldpayrollthat contains the currency code. In your schema you would have declared:Sanjibukai
Thank you very much!
Everything is clear!
Keep up the good work!