Live data from Hacker News

How to Handle Monetary Values in JavaScript

frontstuff.io

111–115 of 115 posts

Re: How to Handle Monetary Values in JavaScript

#111

Earlier quoted context omitted.

> > No, you're generally not throwing out precision when you round in this case. You're throwing out error. > That's not always true from a numerical analysis standpoint. This isn't about numerical analysis, which is what you seem to not be getting. It's about the medium have specific attributes, and floats being incapable of perfectly representing the value. With respect to the currency being tracked, any difference…

> This isn't about numerical analysis, which is what you seem to not be getting. Of course it is. The point of the computation is to get the correct result. Numerical analysis hints at whether your computation will give you the right answer or not. You're asserting that it's trivial to make sure that your fixnums won't overflow, but in most normal cases where you'll fit into a 64-bit int range, you'll also fit into t…

> Of course it is. The point of the computation is to get the correct result. Numerical analysis hints at whether your computation will give you the right answer or not.

In this case, we're comparing a representation which is exact, compared to one which is approximate. The numerical analysis which you are quoting tells you how close (if not exact) your (approximate* representation is. It's irrelevant when compared to exact, because as long as the other benefits you gain (much larger and smaller numbers) aren't needed, that's all downside.

> Your system does not magically become 'safe by design' simply because you use ints everywhere, you still need to put in the extra work to make sure your numeric range is always sufficient—at which point you're doing numerical analysis, whether you choose to call it that or not.

You have to do that work with floating point numbers as well, you just also have to make sure there's not error that needs to be rounded. There is not advantage to floats here, but there is one less thing to worry about with integer values.

> repeatedly make untrue claims about FP calculations

I have made no untrue claims that I'm aware, and I haven't noted you pointing out a specific claim as untrue with evidence that I didn't later point out that you were misinterpreting me on. Please feel free to provide evidence though.

> then wave off the entire subbranch of CS that studies how numbers are represented on computers and how those calculations can go wrong as being entirely irrelevant.

As noted above, it's irrelevant when compared to a representation with no error of the same type. Please stop inflating my assertions to cover more than they directly claimed. This is a straw man argument, please stop.

> As soon as you actually do any 'complicated' math (take a square root or a log), your result is no longer exactly representable, because your results aren't decimals or even rationals, but irrationals.

Care must always be taken with moving between a real currency amount and an interim amount. It makes sense to switch representations at that point, and deal with the amount with a representation that is appropriate for partial values (floating point may be appropriate here). At the point it's stored again, it should be converted to an exact representation again. Partial cents are not valid amounts of currency to have, so it makes sense to deal with the difference before it is represented as a currency again.

> You're contradicting yourself here. All Excel does is 1) compute using floats (doubles) everywhere and 2) round the result before showing it.

I'm saying that Excel isn't only interested in representing currency. If they were, they may have chosen a different representation. Since their constraints are that they also need to be able to accurately represent 0.00001, they are going to choose the best hardware supported representation that support all their needs. That ends up being floating point. For a currency, quite a few of those requirements are no longer needed, so a different trade off is possible.

> That's exactly what I'm advocating. Whether you choose to wrap it into a special Numeric or Money class doesn't change your answer.

> Look, here's C#'s decimal class: https://docs.microsoft.com/en-us/dotnet/csharp/language-refe....

> Must use some fancy arbitrary precision arithmetic right? No, it's just a floating point number—just a particularly wide (128-bit) type.

That's not all it is. It's decimal floating point, not binary floating point, meaning it can exactly represent all the base 10 values in it's range. That is fundamentally different than binary floating point, and the fact they chose this representation is what "makes it appropriate for financial and monetary calculations" in their words, illustrates my point.

Re: How to Handle Monetary Values in JavaScript

#112

Earlier quoted context omitted.

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.

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

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

Re: How to Handle Monetary Values in JavaScript

#113

Earlier quoted context omitted.

> This isn't about numerical analysis, which is what you seem to not be getting. Of course it is. The point of the computation is to get the correct result. Numerical analysis hints at whether your computation will give you the right answer or not. You're asserting that it's trivial to make sure that your fixnums won't overflow, but in most normal cases where you'll fit into a 64-bit int range, you'll also fit into t…

> Of course it is. The point of the computation is to get the correct result. Numerical analysis hints at whether your computation will give you the right answer or not. In this case, we're comparing a representation which is exact , compared to one which is approximate . The numerical analysis which you are quoting tells you how close (if not exact) your (approximate* representation is. It's irrelevant when compared…

[deleted]

Re: How to Handle Monetary Values in JavaScript

#114

Earlier quoted context omitted.

> This isn't about numerical analysis, which is what you seem to not be getting. Of course it is. The point of the computation is to get the correct result. Numerical analysis hints at whether your computation will give you the right answer or not. You're asserting that it's trivial to make sure that your fixnums won't overflow, but in most normal cases where you'll fit into a 64-bit int range, you'll also fit into t…

> Of course it is. The point of the computation is to get the correct result. Numerical analysis hints at whether your computation will give you the right answer or not. In this case, we're comparing a representation which is exact , compared to one which is approximate . The numerical analysis which you are quoting tells you how close (if not exact) your (approximate* representation is. It's irrelevant when compared…

> At the point it's stored again, it should be converted to an exact representation again. Partial cents are not valid amounts of currency to have, so it makes sense to deal with the difference before it is represented as a currency again.

Exact decimal representation doesn't prevent rounding errors from accumulating over a chain of computations because oftentimes the correct value cannot be represented as an exact decimal. Forcing it by repeatedly rounding to the nearest currency unit, which is what you're proposing above, compounds the problem by needlessly throwing away available precision.

Simple example is paying interest on a bank account. If you insist on only storing account balances as an exact 2-digit decimal number, then any compound interest computation will quickly accumulate large errors, e.g.:

  // $100 in 'exact' integer form
  let float_balance = 10000
  let rounded_balance = 10000

  const daily_interest = 0.10 / 365 // 10% rate, compounded daily

  // Pay interest for 5 years
  for (let i = 0; i 
'Exact representation' does not ensure that your computation remains free from roundoff error, and sometimes makes it much worse. If you really insist on 'only storing as many digits as there exist in the currency' in your DB, then you will systematically underpay interest to all your customers... in this example, you'll actually start shortchanging your customer in just a few weeks.

It's your extra forced rounding to pennies that introduces the error: in reality, you really do owe fractional cents before they tick over into the next penny, even if you don't show it in the bank statement. The easiest way to solve the issue here is to store the account balances as floats, which keep track of the 'fractional pennies' which you claim are meaningless. Or forget about floats -- just use fixnums, but keep are the lower digits rather than discarding them upon storage.

By the way, this is the whole point of numerical analysis — you cannot blindly assert that 'if my numerical representation is exact, then my computation will always be correct / minimize total errors'. Yes, the logic is simple and seductive. The conclusion is also completely, utterly, stupendously, insidiously wrong. I hope you don't actually work in a financial context, because this will bite you in the ass someday if you lean hard on it.

I don't really like to appeal to authority, but it was literally my day job for a few years to make sure the numbers were right. Half my team were physics PhDs, and the rest were usually math PhDs or MFEs. We didn't use floats because we were sloppy, we used them because they typically were the most reliable way to get the most accurate result, in a domain where most numbers represented currency or money.

Only when you are actually sending or receiving money from the outside world (e.g. on an invoice, wire transfer, or transaction) should you rounding to the nearest decimal, and at that point, it might be appropriate to use a decimal or fixnum type to exactly represent the monetary value that was actually moved, but again this is typically overkill since you can always just store the rounded amount as a float. This is safe because rounding is idempotent: round(round(float)) = round(float)

But internal monetary calculations are best kept in the same type with enough extra precision to do computations, and floats are often the correct choice here, with no forced intermediate rounding. If you need 2 accurate decimal digits and you insist on using fixnums rather than floats, then you should be passing around at least micros ($1e-6).

Re: How to Handle Monetary Values in JavaScript

#115

Earlier quoted context omitted.

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

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

The only rule is no splitting the bill
Post reply on HN