Are there any things to look out for or be aware of when using Elixir (Erlang) when developing financial applications, specifically when doing calculations on monetary values?
Please use the decimal library , and don’t use the comparison operators (use Decimal.compare)
Mind that decimal and other libs using it underneath are quite slow. In our use case it turned out to be about 100x slower than full precision integer arithmetics.
The problem is that it can be irritating to work with if you want to handle all currencies. Not all currencies are split into 100s (some are to 1000s and other aren’t split at all).
It really depends on what your use case is. Not all financial calculations are done in currency units - some are unitless, some use other units (e.g. 1/[currency], [currency]/[time]). Even those that are in currency can require different precision (e.g. EURUSD exchange ratio will rather have bigger precision than EUR and USD currencies). A common practice for calculations in currency unit is to do them in extended precision and round to currency decimal points afterwards. BTW it’s even more complicated as currencies can change decimal points over time (e.g. smallest amount of CZK used to be 0.01, then 0.1 and now 1).
Financial calculations should (in my opinion, but based upon reasonable research) always be done at full precision and only rounded, per the requirements of the currency, when presented to the user or as required to balance a ledger (and then also should take care of any remainder). ex_money makes all of that transparent and uses the currency definitions in CLDR for the reasons you mention - different currency rules on decimal digits, rounding and formatting as well as localisation.
Decimal library is, under the covers, just integer operations but as you point out, keeping track of mantissa and exponent and scaling correctly does make it quite a bit slower than straight integer operations. But at some point, resolving the required precision, rounding the number, rounding to the nearest (for cash calculations in CHF, AUD and others) still have to be done.
Given that money is a complex enough domain to get right - and where rightness is pretty important - then a library should most likely suit unless the use case is constrained (well known list of currencies, specific performance requirements, …).
First let me say how much I appreciate the thoughtful and detailed comments in response to my question.
And RE “…or as required to balance a ledger (and then also should take care of any remainder)”, my intention is to write an accounting program, so the addition and subtraction of monetary amounts when posting journal transactions to the general ledger is the specific use case behind my question.
So, in the absence of multi-currency support, is it fair to say that maintaining accurate account balances in any one, single currency should not present any significant challenges?
Thanks again for all of the input already received.
Elixir as a language doesn’t put any barriers in the way of managing accurate amounts. The things to think about are:
- Never use floats for representing money. Ever.
- Use Decimal simply because it implements arbitrary precision arithmetic and you never have to think about where to put the decimal place (as you do with integer representations). For sure you can use integers, but see (3) below.
- If you are only ever working in a single currency (and you might have to think about what happens when your first customer asks for just one more currency …) then addition and subtraction won’t present any issues.
- If you have to do multiplication or division (think interest, pv, fv, dividend calculations) then you are back in the realm of having to ensure you decide when to round numbers and what to do with the remainder after rounding.
- When serialising to a database, make sure the debit and credit postings are atomic (ie only ever do them together in a single database transaction) otherwise madness will ensue.
- Depending on the locales of the users for the application, pay attention to parsing money amounts. Different cultures us
.for grouping and decimal separators in opposite ways with unexpected results unless you plan in advance (or can guarantee a single locale support)
- Don’t forget that negative money amounts can exist in a ledger
- Test, test, test.
You might find this helpful too: Falsehoods programmers believe about prices.