Live data from Hacker News

How to Handle Monetary Values in JavaScript

frontstuff.io

61–70 of 115 posts

Re: How to Handle Monetary Values in JavaScript

#61

> 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 f…

And I had the misfortune for working for a few years with bond modeling software that used floats internally.

Turns out that the first thing a financial analyst does to compare two implementations of a bond securitization model is to look at the pennies. If those tie out, then you're good. But if your software predicts money off by a dime in a billion dollar model deal, the analyst WILL notice and WILL try to get to the bottom of it.

Yeah, floating point roundoff error seems like it should be immaterial. However it can matter a whole lot.

Re: How to Handle Monetary Values in JavaScript

#62
post #60

> 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 f…

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…

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.

Re: How to Handle Monetary Values in JavaScript

#63
post #3

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.

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

Sure, split an amount in 3 payments - take one dollar, put 0.333333 dollars once, 0.3333333 dollars the second time, and the remainder (1 - 0.33333333 - 0.33333333) as the outstanding balance doesn't match what you should have; you should have had 0.34 but you have 0.33 if you round only at the end.

Do the same with a proper decimal datatype, and the same operations give the correct result - pay out 1/3=0.33; pay out 1/3=0.33; the last payment is 1-0.33-0.33 = 0.34 exactly.

The same goes with all kinds of other issues e.g. totalling up tax or discount amounts (calculated as a percentage) where you'll get mismatches if rounding happens at the wrong time, etc.

E.g. ten separate receipts each selling a 0.10 item with a 9% tax or discount will result in each receipt having 0.01 tax or discount; so the proper total in this case is 1.00 sales with 0.10 tax or discount; different than the case if you sell ten such items in a single transaction. If you do all the calculations in floats, you get this wrong.

Re: How to Handle Monetary Values in JavaScript

#64
post #60

Earlier quoted context omitted.

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…

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.

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.

Re: How to Handle Monetary Values in JavaScript

#65

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/

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

Re: How to Handle Monetary Values in JavaScript

#66

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/

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

From the docs

> You must provide your own API to retrieve exchange rates.

https://sarahdayan.github.io/dinero.js/module-Dinero.html#~c...

Re: How to Handle Monetary Values in JavaScript

#67
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 
(I assume that if I knew JavaScript I could do something much nicer than that while look for the padding).

Re: How to Handle Monetary Values in JavaScript

#68
post #24

> 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.

How much of an issue is it?

The kind of issue do not want to have: issues with money that can go very wrong when calculating discounts, tax percentages, interest or whatever.

Re: How to Handle Monetary Values in JavaScript

#69
post #60

> 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 f…

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…

I'm totally with you. We write/run financial software for government. We MUST be correct to the penny. Our auditors absolutely check this stuff.

We've never done $ math in JS, that just seems like a stupid place to do it. If it's in the browser, you can't trust anything that happens there anyways, as it runs client side, with almost zero security. We happily take $ input via JS on the client, but we just pass it through, and let the server(s) handle the actual math.

Re: How to Handle Monetary Values in JavaScript

#70
post #34
post #4

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

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 .

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.

Post reply on HN