Sanjibukai

Sanjibukai

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?

Showing Posts 1 to 6

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.

Sanjibukai

Sanjibukai OP

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

Sanjibukai OP

Just a quick question, is it necessary to set both ex_money and ex_money_sql or 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_sql and it brought ex_money by itself..
But what’s the best practice when defining dependencies. Is it better to explicitly declare ex_money too?
I guess that it’s okay to only declare the later since it’s the same for ecto on Phoenix projects where only ecto_sql is used on the deps list…
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_sql with 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 by ex_money_sql performs relative to the default Postgres sum aggregate function?

I see that it’s also possible to use Repo.aggregate with 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

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
Sanjibukai

Sanjibukai OP

Thank you very much!
Everything is clear!
Keep up the good work!

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews