1

I had to design a money-handling application recently that was fully in javascript, express.js, REST apis, the full node/browser js stack. Unfortunately, the application dealt with a variety of custom calculations like averaging out the results. Of course, when averaging results, floating point addition and division comes into play. Thus, I would get values that were off by about 0.12 (12 cents!).

I understand the recommended options for dealing with floats in theory:

  • Calculate in integers
  • Round
  • Use a decimal datatype somewhere else

The integers-for-currency approach is fine, that would make sense, but unfortunately if requests are coming in in decimal form, then some kind of calculation to initially convert floats to integers still has to happen, and the potential for error is still retained.

So let's say a request comes in to add 44.67 to a list of dollar values. It'd be nice to work with it in clean integer units, but to get there requires multiplying by 100 first, which I am suspicious causes the whole set of errors to come with it.

So in javascript what is the process for handling smooth conversion between decimal or dollar requests and calculations that avoid floating point errors?

In that case is a database required, and the values just have to be passed in to the database as strings? Or is there a way to convert "losslessly" to integers within the javascript? Or is there some other technique when handling money and requiring exactness?

Kzqai
  • 22,588
  • 25
  • 105
  • 137
  • 2
    Of course you'd have to round to an actual integer after multiplying by 100. There's no error introduced by that. – Bergi May 22 '19 at 21:14
  • 1
    ... In other words: `Math.round(value * 100)` will always be 123 for 1.23 – Jonas Wilms May 22 '19 at 21:16
  • @Bergi, that doesn't always work and can still lead to incorrect values in some cases where the fractional component extends past the first few places*. There is a more thorough discussion of this topic, and an answer to the question, located here: https://stackoverflow.com/questions/2876536/precise-financial-calculation-in-javascript-what-are-the-gotchas#comment2924188_2876619 – Propagating May 22 '19 at 21:16
  • Can you write a test that will fail given a float with two decimals that is added to 100 not producing the correct integer value? Would love to see that. – Randy Casburn May 22 '19 at 21:19
  • @Propagating As long as the amount can be properly represented as an integer, it should work - or do you mean something else? – Bergi May 22 '19 at 21:19
  • Try Number.parseFloat(num).toFixed(8); – LJD May 22 '19 at 21:20
  • 1
    Good question. If you really want to avoid floating point calculations (rightly so), and assuming that input strings always have 2 decimal digits, you can remove the `.` from the input and convert to an integer using `parseInt()`. IEEE 754 (64 bit) works fine with integers up to 2^53 (if I'm not mistaken), so you can handle all calculations in minor units (cents) from there. As long as you're not dividing, you're covered. – BenMorel May 22 '19 at 22:14
  • 1
    @Bergi: “There’s no error introduced by that” is not a true statement. If the input is “1000000000000001”, the result of parsing the input should produce 1000000000000001, since it can be represented exactly, but multiplying that by 100 will produce 100000000000000096, and rounding cannot fix it. Of course, it is not likely OP is working with a quadrillion and one dollars, but they might be if they are working with Zimbabwean dollars. – Eric Postpischil May 23 '19 at 10:32
  • 1
    @Propagating: If the initial input is at most 14 decimal digits (including two after the decimal point), then multiplying by 100 and rounding to the nearest integer always produces exactly 100 times the initial input. (Actually, I think it is true for 15 digits too but would want to draft a proof before asserting it.) It is possible OP is running into this limit, per my above comment, but it is unlikely. – Eric Postpischil May 23 '19 at 10:34
  • You should provide a [mcve]. – Eric Postpischil May 23 '19 at 11:02
  • @EricPostpischil Yes, I meant what you wrote in your second comment :) – Bergi May 23 '19 at 12:06
  • Related question: https://stackoverflow.com/questions/2876536/precise-financial-calculation-in-javascript-what-are-the-gotchas#comment2924188_2876619 – Kzqai May 23 '19 at 15:09
  • Related question: https://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript – Kzqai May 23 '19 at 15:09
  • Related question: https://stackoverflow.com/questions/19473770/how-to-avoid-floating-point-errors – Kzqai May 23 '19 at 15:09

1 Answers1

1

Potential sources of error in the problem as described in the question include:

  • Input values are so large they cannot be multiplied by 100 and rounded to the nearest integer without error. (This requires input values around 1013 or larger, so it is not likely unless the data involves trillions of dollars.)
  • The calculations performed could result in values so large that rounding errors occur.
  • The “custom calculations” mentioned in the input include things that have rounding errors even if the values are small integers. For example, “averaging” is mentioned, and the average of 1 cent, 1 cent, and 2 cents cannot be computed in binary floating-point without rounding errors, since 4/3 is not representable. Additionally, “custom calculations” could include logarithms, evaluations of probability distributions, integrations, and other mathematics beyond elementary arithmetic.
  • There could be bugs in the code.

So in [JavaScript] what is the process for handling smooth conversion between decimal or dollar requests and calculations that avoid floating point errors?

Regarding the former, if x is a number represented in input characters in decimal with at most two digits after the decimal point and is not excessively large (per the above), and x is the result of correctly converting that input to a JavaScript Number, then the result of Math.round(100*x) is exactly 100x. There is no net rounding error. The sources of any calculation errors lie elsewhere.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312