Sanjibukai

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?

Most Liked

kip

kip

ex_cldr Core Team

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:

  1. Rounding to the right number of decimal digits. 0,1,2, 3 and 4 digits are in use with different currencies around the world.

  2. Use the right rounding mode. Typically you would use bankers rounding which, in the Decimal library is mode :half_even and is also the default in ex_money.

  3. Apply any necessary round nearest for 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.

hex> m = Money.new(:USD, "5.23")
#Money<:USD, 5.23>
iex> Money.div m, 3
{:ok, #Money<:USD, 1.743333333333333333333333333>}
iex> Money.div!(m, 3) |> Money.round
#Money<:USD, 1.74>
iex> Money.div!(m, 3) |> Money.round |> Money.mult!(3)
#Money<:USD, 5.22>

Where did the last cent go? Thats the reason I implemented Money.split/2.

iex> Money.split(m, 3)
{#Money<:USD, 1.74>, #Money<:USD, 0.01>}

Which allows you to decide what you do with the amount left over. Oh, and Money.split/2 takes 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

kip

ex_cldr Core Team

ex_money will 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/2 will 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.

kip

kip

ex_cldr Core Team

Updated the docs for ex_money since it is a major version change. I left the ex_money_sql docs alone since hex will pick up the latest version automatically.

Just ex_money_sql is 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):

query = from o in Organization,
          where: fragment("currency_code(payroll)") == "USD",
          select: sum(o.payroll)

Basically you have to wrap the composite value component in a fragment since its not natively supported in Ecto since its not cross-db compatible syntax. In this example, currency_code is the component of the composite field payroll that contains the currency code. In your schema you would have declared:

field :payroll, Money.Ecto.Composite.Type

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Other popular topics Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39467 209
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement