
How to Handle Monetary Values in JavaScript - fagnerbrack
https://frontstuff.io/how-to-handle-monetary-values-in-javascript
======
projectileboy
Maybe it’s just that I’ve done a few gigs at financial institutions, but it’s
pretty shocking to see the number of people in this thread arguing that it’s
a-ok to use floating point for money. I don’t want to get dragged down into
specifics, but if you’re doing this, please, please don’t. Money is one of
those things like dates and times - everyone who hasn’t done much of it can’t
figure out what all the fuss is about. Just... please, at a minimum, if you
don’t want to be bothered, find a reputable third-party library with Money and
Currency or some such, and use that. It really won’t add much work for you.

~~~
smallnamespace
I'm curious to hear about your specifics, since I was at a shop where front
office used all floats (doubles).

Not sure what the back office did, but I doubt it was all done in arbitrary
position arithmetic.

Note that if an accountant says things need to be accurate down to the penny,
that's probably an exaggeration [1]. Financial statements are typically
rounded to the thousands, e.g. Intel's are rounded to million [2].

The IRS also lets you round off dollars [3].

I agree though that if you're building a large complex system with many
coders, and either 1) nobody has a full view of the calculation and can do the
numerical analysis or 2) there's a high chance that at some point in the
future you need penny-accurate bookkeeping, then it's safer to use a very wide
int or float type.

Of course, there's also the case where the accountant or auditor doesn't
actually _need_ penny precision, but it's easier to just tell the coder 'just
in case'.

[1] [https://www.accountingtools.com/articles/2017/5/14/the-
mater...](https://www.accountingtools.com/articles/2017/5/14/the-materiality-
principle)

[2]
[https://s21.q4cdn.com/600692695/files/doc_financials/2017/an...](https://s21.q4cdn.com/600692695/files/doc_financials/2017/annual/Intel_Annual_Report_Final_corrected.pdf)

[3]
[https://taxmap.irs.gov/taxmap/pub17/p17-006.htm#TXMP5d034bfb](https://taxmap.irs.gov/taxmap/pub17/p17-006.htm#TXMP5d034bfb)

~~~
kbenson
If you bill customers for anything, and there are multiple items being totaled
on an invoice, you do _not_ want that total to be anything except an accurate
sum of the individual parts.

At a minimum, you look like a rinky dink operation that doesn't know how to
add dollars and cents. On the other end of the spectrum, some people will
likely accuse you of theft, and not nicely.

Do not fall under the assumption that the amount you mis-bill will inform how
upset the people are that contact you. You will likely be the easy and obvious
scapegoat for all their recent annoyances. For example, imagine all the people
upset at their AT&T, Comcast or Verizon bill, who now have an easy and obvious
thing to point at as an example of over billing. Have fun with those
transferred emotions...

~~~
smallnamespace
I think that's the disconnect here — 'high' finance is _not_ an exact science,
and clients don't actually have an easy way of checking the numbers directly
themselves — not only the models, but the inputs are also proprietary. Even
auditors and regulators are dependent on bank models.

Note that the article's advice is to use their own library, but the library
just uses JS floats as ints and a separate precision arg:

[https://github.com/sarahdayan/dinero.js/blob/master/src/dine...](https://github.com/sarahdayan/dinero.js/blob/master/src/dinero.js#L47)

This doesn't give you any more precision than using floats directly, just a
wider 'mantissa' range.

~~~
haberman
> This doesn't give you any more precision than using floats directly, just a
> wider 'mantissa' range.

I think this analysis misses a deep and important point. The library you
linked uses _decimal_ floating point (in software) instead of the binary
floating point used by IEEE floats.

The point of this is not to increase overall precision. A double is already
precise to 2e-14% of its value, which means it's capable of representing
something like $1 Trillion before it will get off by a whole cent.

The point of using decimal floating point is that it can exactly represent the
values we care about when dealing with currency. This allows us to have no
error, instead of trying to keep our error small. It means we never have to
round (unless performing division).

You could have 1000x the precision in binary floating point, and decimal
floating point would still be better for money.

~~~
smallnamespace
> This allows us to have no error

That's not true, because the software 'mantissa' in that library still only
has 52 bits of precision (2e-14%).

E.g. if you try to add $100 trillion + $0.01 using dinero.js, you still get
underflow and the $0.01 disappears.

------
b2ccb2
I really like the approach Perl 6 takes with FatRat[1]. You basically have an
object which holds a numerator and denominator, so all arithmetic calculations
do not loose precision.

[1] [https://docs.perl6.org/type/FatRat](https://docs.perl6.org/type/FatRat)

~~~
tikhonj
Rational numbers don't lose precision, but the compromise is that they can get
arbitrarily large after a series of calculations, even if the calculations
themselves involve small numbers.

Having _access_ to rational numbers is great and there are times they are
incredibly useful, but I don't think it's a good _default_.

~~~
dpwm
For monetary values, though, the denominators should be the same.

This means that the denominator won't get arbitrarily large.

For serious uses of rationals you need to have control over when the top and
bottom are divided by the GCD. For currency you want to keep the denominator
at 100. 50/100 wants to remain as that and not get simplified to 1/2.

~~~
Retra
If you have a constant denominator, you don't need it. It's just a unit.

------
reaperducer
_Banking apps, e-commerce websites, stock exchange platforms, we interact with
money daily. We also increasingly rely on technology to handle ours.

Yet, there’s no consensus around how to programmatically handle monetary
values._

I wonder if the average person would find this frightening.

~~~
rpeden
I don't think it's true that there's no consensus around how to
programatically handle monetary values. Maybe there's no consensus around how
to do it in JavaScript.

In the Java world, there's JSR-354:
[https://jcp.org/en/jsr/detail?id=354](https://jcp.org/en/jsr/detail?id=354)

Cobol has handled money well for decades.

.NET has Decimal, which should handle money correctly as long as you're
working with values less than 79.2 octillion.

In my part of the world, at least, big banks tend to be mostly Java and Cobol
on the back end. You'll definitely see Node, Go, and others being used for
various tasks throughout the company, but when it comes to keeping track of
money and moving it around, it's primarily Java and Cobol doing the heavy
lifting.

That's what I've heard from other developers, anyway. I haven't worked at a
bank myself.

~~~
kickopotomus
Exactly correct. More stuff than most people expect is still handled by COBOL.
I haven't worked with a bank in a few years, but back in 2015 the one I was at
was attempting to migrate a lot of the COBOL tasks to Java.

------
DataInSolutions
My goto for any kind of arbitrary precision math in JavaScript is
[https://github.com/MikeMcl/big.js](https://github.com/MikeMcl/big.js)

As others have noted, if you're doing any kind of math that involves floating
point numbers and you will displaying them to a user, you'll almost certainly
want something like this.

While it's not specifically a "Money" class it can form an excellent base for
one.

------
empyrical
Really excited to see what possibilities for number handling native BigInt in
JavaScript will bring

[https://developers.google.com/web/updates/2018/05/bigint](https://developers.google.com/web/updates/2018/05/bigint)

~~~
zie
probably none. To handle currency correctly you have to handle rounding to the
nearest penny correctly. Computers absolutely SUCK at this, in general. So you
need a Decimal library, that can handle decimal numbers. Integers _ARE NOT
DECIMAL_.

IBM has had a solution for a long time:
[http://speleotrove.com/decimal/](http://speleotrove.com/decimal/)

Python uses it:
[https://docs.python.org/3.5/library/decimal.html](https://docs.python.org/3.5/library/decimal.html)

Get a proper decimal library, and then you can devote brain cells to actual
problems. The IBM link above includes links to libraries that do it right.

~~~
village-idiot
You can also just use the smallest unit and work in cents. The Stripe API does
this instead of dealing with decimals.

------
cpburns2009
I looked into using Dinero.js before but it was overkill for my use case of
calculating cart totals in USD only. I went with Big.js because it was better
suited for what I wanted: a small library for decimal arithmetic.

~~~
tlb
Can you illustrate a case where you can't use regular FP arithmetic and round
to 2 decimal places at the end?

~~~
ekke

      (1.005).toFixed(2) // Expected rounding to "1.01"
      "1.00"

~~~
noselasd
cases like that's everywhere.

2.875.toFixed(2) = "2.88" while 2.675.toFixed(2) = "2.67"

7205759403792794 + 1.1 = 7205759403792795

7205759403792794 + 1.9 = 7205759403792796

------
zachrose
I wish you could add different currencies together:

    
    
        Dinero({amount: 5000, currency: 'USD'})
          .add(Dinero({ amount: 1000, currency: 'EUR'}))
    

...and then convert that sum to a concrete currency later.

This feature request inspired by [http://blog.ploeh.dk/2017/10/16/money-
monoid/](http://blog.ploeh.dk/2017/10/16/money-monoid/)

~~~
diNgUrAndI
How would you deal with exchange rate, which changes in real time?

~~~
zachrose
In the lazy version i have suggested, the exchange rates are deferred until
you “need to know” and the sum is converted into a concrete currency.

If you’re keeping these values around in memory for an extended period of time
time and you care about rapid changes to exchange rates, then you have a
complex domain and the deferred exchanges may or may not be a good fit, but at
that point you need larger system design, not a JavaScript library.

Also in my dream money library, division and multiplication would be done with
whole number fractions like

    
    
        new Dinero({amount: 1000, currency: ‘usd’})
        .multiply({numerator: 1, denominator: 3 })
    

which can similarly be deferred to prevent rounding errors in partial
calculations.

~~~
kchoudhu
You need a timestamp and a timeseries database of exchange rates so that you
can go back in time and reconstruct your calculations at a latter date as
well.

Source: worked at a bank that implemented pretty much this.

~~~
swish_bob
To multiple different currencies at multiple valuation points. With optional
exchange rate costs. It's a pain in the arse ...

Source: worked on multi-currency fund tracking software.

~~~
zachrose
Agreed with both of you, and using an exchange rate at a specific point in
time actually could make deferring that calculation even more of a sound idea,
depending on the exact semantics of what you’re computing.

At larger scales even exchange rates are a leaky abstraction over a forex
market, so there you go. No solution will work everywhere.

But if you’re entering the prices of last week’s coffee and hotel room into a
web app to see how much you’ll be reimbursed, you don’t want to use a new
exchange rate every time a form input changes.

Source: worked on multi-currency expense reporting software where everything
is done in JavaScript floats.

~~~
kchoudhu
We should all start a club defined by our forex market induced alcoholism.

~~~
zachrose
The only rule is no splitting the bill

------
gvx
While reading, I thought "hang on, what about non-decimal currencies?" but it
turns out that is no longer a concern for modern usages:
[https://en.wikipedia.org/wiki/Non-
decimal_currency](https://en.wikipedia.org/wiki/Non-decimal_currency)

~~~
dpwm
You can handle non-decimal currencies by treating money as a fraction.

You can implement a correct set of fraction manipulation functions in well
under 100 lines of code with a bit of algebra. It seems likely there are
several well-tested high-performance fraction libraries for JavaScript. They
should be tiny.

Some people in the UK apparently voted to leave the EU so that they could have
a blue passport [0], perhaps there are some who also hope we return to
imperial measurements and pounds, shillings and pence. I'm quite sure that
even in that unlikely event, some programmers will still insist on using
floating point numbers for currency.

[0] Although Malta has a blue passport and is in the EU. And the only reason
the passports were blue in the first place was to meet an international
standardization.

------
rtkwe
> Floats: 0.1 + 0.2 // returns 0.30000000000000004

The question is how much of an issue is this really? How often is 4 parts in
10 quadrillion a meaningful error when dealing with money? Especially when
most of the time JS is dealing with money it will be presentation.

~~~
jczhang
Yeah seriously. You'll have some front-end lib func converting that to 2
significant digits right of the decimal anyway. Weak argument imo haha.

------
protomyth
_Yet, there’s no consensus around how to programmatically handle monetary
values._

Do what COBOL does and use it to verify your monetary library. Your code will
agree with the majority of financial institutions.

------
kisstheblade
I usually use cents as the stored amount, so no decimals. Is there something
obviously wrong with this approach?

Ie. if the price is 150.95 I store 15095 in the db and do the calculations
with cents, and then divide by 100 when presenting the values.

I have never come across fractional cents, maybe that is an issue in some
scenarios (would be interested to know what those are... Even tax percentage
calculations are rounded to the cent)

------
bschwindHN
Handling currency in JavaScript just sounds like a bad idea. It's better to be
using a language with a type system that has your back on this kind of thing,
and preferably to deal with monetary values as rational numbers.

There's a good article on this subject here:

[https://ren.zone/articles/safe-money](https://ren.zone/articles/safe-money)

------
mjevans
Equally important to choosing a valid numeric type to store the data is solid
business logic to handle it.

In the contrived example of splitting a value of 999.99 among more than one
payment, the correct solution is:

    
    
        * Determine the next payment based on the current balance.
        * Invoice/Apply/Etc that payment
        * Repeat until there is a balance of 0 (zero).

~~~
lowercased
recording an incoming payment and applying that payment value to specific
accounts is its own thing to be aware of. People want to see that they paid
$200, even if it was $150 to one account and $50 to account.

------
tantalor
This advice doesn't have anything to do with JavaScript until the author
starts plugging their own library.

------
brennankreiman
This seems like the basis for stripe handling amounts in cents to maintain
integer values.

------
r32a_
Many cryptocurrency libs like Web3 opt to use bignumber js for all money
manipulation

------
peheje
Nice and simple writeup. Straight to the point without being ceremonious.

------
krapp
Don't.

For the love of God, just don't.

------
tzs
A while back I did some exhaustive testing of several ways to calculate sales
tax on a sale in Python3 and JavaScript, testing the various "obvious" ways
against all tax rates in increments of 0.0001 from 0.0000 to 1.0000 and all
sale amount from 0.00 to something like 25.00.

It was interesting. Here are some simple cases to try. One or more of these
break most of the seemingly obvious approaches:

    
    
       1% of $21.50
       3% of $21.50
       6% of $21.50
      10% of $21.15
    

Here are some examples of obvious looking but wrong approaches. These all
return the tax in pennies, so the desired results are 22, 65, 129, and 212. In
puts are floats, such as 0.01 for 1% and 21.50 for $21.50.

    
    
      # 1%, 10% wrong
      def tax_f1(amt, rate):
        tax = round(amt * rate,2)
        return round(tax * 100)
    
      # 3%, 10% wrong
      def tax_f2(amt, rate):
        return round(amt*rate*100)
    
      # 6% wrong
      def tax_f3(amt, rate):
        return round(amt*rate*100+.5)
    

Here are two that do work:

    
    
      def tax_f4(amt, rate):
        amt = round(amt * 100)
        rate = round(rate * 10000)
        tax = (amt * rate + 5000)//10000
        return tax
    
      def tax_f5(amt, rate):
        amt = int(amt * 100 + .5)
        rate = int(rate * 10000 + .5)
        tax = (amt * rate + 5000)//10000
        return tax
    

If instead of floating point input, you work with numbers already scaled up to
integers (scaling the rates by 10^4 and the money by 10^2), this works:

    
    
      def tax(amt, rate):
        tax = (amt * rate + 5000)//10000
        return tax
    

The last three approaches, (tax, tax_f5, and tax_f4) also work well in PHP,
Perl, and JavaScript.

In my code, I attach a suffix to rates and moneys that are scaled this way
telling the scale factor, so I might have tax4 and cost2, meaning the tax rate
is x10^4 and the cost is x10^2. I was disappointed to find that neither Perl
nor Python would let my use Unicode subscript numbers in my variable names.
:-(

I also did a series of exhaustive tests where I'd take every string of the
form every string of the form digits dot digits with up to N digits after the
dot, parse them with the languages most natural string to float converter,
multiple that by 10^N and round to an integer, and verify that this was the
"right" integer, and also verified that doing a floating point divide of that
integer by 10^N and then using the most natural way in the language to turn
that to a string with N digits after the dot gave the right result.

The idea here was to convince myself that I would not run into problems at the
"convert to integer" or the "convert from integer" part. I did these tests in
C, Perl, Python, and JavaScript I _think_. I'm having trouble finding my code
now so I'm not sure if I tested in all of them.

Anyway, I concluded that it was safe. It's only when you start computing in
float point that you have to worry.

Finally, in most of what I was doing at the time, I actually didn't ever need
to convert back to a floating point format. All I was going to do with the
final price, computed from cost + tax, was just display it to the user...and
so if I converted to floating point I'd end up just sprintf'ing it (or
equivalent) back to a string. All sprintf would really be being used for was
to make sure it had the right padding and leading zeros and stuff like that.

So instead of going through floating point, I just did this (JavaScript):

    
    
      function cents_to_dollars(cents)
      {
        cents = cents.toString();
        while (cents.length < 3)
            cents = '0' + cents;
        return cents.substr(0,cents.length-2) + '.' + cents.slice(-2);
      }
    

(I assume that if I knew JavaScript I could do something much nicer than that
while look for the padding).

~~~
zie
why didn't you just use the decimal module[0]. It's been in Python since 2.4
and just handles penny problems for you, basically zero brain power required.

0:
[https://docs.python.org/2/library/decimal.html](https://docs.python.org/2/library/decimal.html)

------
smallnamespace
> Using floats to store monetary values is a bad idea

This is waay overstating the case, it very much depends on whether getting the
exact answer down to the penny matters to you.

Worked for a few years at a large investment bank, everything was done in
floats because the _modeling error_ of your derivatives pricers would be much
larger than the roundoff error, but floats were much more convenient to
develop with and faster since you avoided a bunch of casts.

Most of the time people think they need things down to the penny, they
actually don't. An accountant does not care if you rounded off a few thousand
dollars in your $10bn a year income statement.

A customer does not really care that you paid them $0.33 three times, rather
than $0.33 twice and $0.34 on the third transaction (and fixnums don't even
solve this 'extra precision' case without a bunch of extra work).

~~~
jimmy1
I _really_ want to be generous here: I think your experience is very isolated.
I have family that is executive level at a Wall Street bank, they would have
you fired over the superfluous loss of a dollar, much less a few thousand
dollars because that type of rounding error can end up in the millions over
the course of a year if you deal 100+ million dollar transactions 5-6 times a
day.

Here's how it would go:

"Why are we losing a couple of thousands of dollars?"

"Well it was easier to just work in floats"

"Pack your shit and get the f--- out of here, now."

~~~
endorphone
It really depends upon the context. If they're talking about reporting and
forecasts, floats are almost certainly fine. The back end actual storage is
generally always numerics, however.

~~~
PeterisP
Depends on the reporting - if it's legally mandated reporting or tax
reporting, then it's going to get audited and any rounding mismatches are
definitely not okay.

