0

Dude to floating point precision in javascript (i think) multipling 257.78 * 100 gives me 25777.999999999996. What is the correct way to do this? Do I use Number.EPSILON somehow?

Currently I am now rounding: Math.round(257.78 * 100), but I'm thinking using EPSILON somehow might be more correct?

There is another question here but an answer with EPSILON considered is not given, he just says round - I worry if the floating point error adds up during multiplication round will fail - Javascript Floating Point Multiply by 100 Still has Errors

Noitidart
  • 35,443
  • 37
  • 154
  • 323

3 Answers3

1

When you do multiplication, errors add up. Therefore the error will be a magnitude larger than the error of the fraction. But it is still usually a lot smaller than 0.1, and therefore it will usually not cause Math.round to behave in an unexpected way. This piece of code is totally fine (as long as you don't use it with very large / very small numbers).

proof:

The maximum rounding error between a number x and it's (imprecise) representation rd(x) fullfills the formula | (x - rd(x)) / x | < 2 * E, or in other words: The error grows linear to our number x, and will always be smaller than |x| * 2E (E being the Machine Epsilon - the largest possible relative error).

For floating-point multiplication of a*b, the result is a*b = a*b*(1 + k), whereas k < E. Therefore if we combine the error of representation and multiplication we end up at:

rd(rd(a) * rd(b))
= (a + e1)(b + e2)(1 + e3)

Now assuming the worst case (that e1 is |x| * 2E), it results in:

rd((a + |a| * 2E)(b + |b| * 2E))
= rd(a * b + a * |b| * 2E + b * |a| * 2E + |ab| * 2E)
= a * b + a * b * E + a * |b| * 2E + a * |b| * 3E + b * |a| * 2E + b * |a| * 3E + |ab| * 2E + |ab| * 3E
= a * b + |a * b| * (E + 2E + 3E + 2E + 3E + 2E + 3E)
= a * b + |a * b| * (15E)

That is, our relativ rounding error is 15E at max. Given that our rounding error has to be smaller 0.1 for the rounding to work correctly, this results in:

0.1 > |a * b| * (15E)
0.1 / 15E > |a * b|
30023997515803 > |a * b|

Given that a = 100, we get b < 300239975158.

Note that this is a worst case estimation. I checked a few numbers above that border, and couldn't find anything off. The first calculation going wrong was above the maximum safe integer range, and then it's obvious that the result can't be right:

console.log(
  Math.round(90071992547409.93 * 100) === 9007199254740993,
);

For a proof of the formulas used, see What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • I don't know why this post was downvoted. I think it's the solution. I was worried `Math.round` would fail, but no matter the multiplication the error will always be less than `0.001`? Thank you I'll accept this and up vote to remove the downvote. – Noitidart May 16 '20 at 23:51
  • 1
    @noitidart I *think* the multiplikation of `a` and `b` with errors `e¹` and `e²` (~ Epsilon) will be `(a + e¹)(b + e²) = ab + ae² + be¹ + e¹e²`. The `ab` is actually what we wanted, `e¹e²` is very very small, and `ae² + be¹` is actually the error we have to worry about. – Jonas Wilms May 16 '20 at 23:57
  • 1
    Given that error is something in the range 10e-26, as long we deal with numbers smaller 10e24 this should not fail. – Jonas Wilms May 17 '20 at 00:01
  • I'm not the downvoter, but I suspect they may have taken issue with the statement that it "it will never cause Math.round to behave in an unexpected way" - if the numbers get large enough, it could in fact cause it to behave in an unexpected way. If you really need it to always behave as expected, you need to ditch floating point and use something like BigDecimal. – Ryan M May 17 '20 at 00:05
-1

You can try this if it fits your needs:

(257.78 * 100).toFixed(2)

If you want it to be a number instead of a string:

parseFloat((257.78 * 100).toFixed(2))

Talha
  • 807
  • 9
  • 13
-2

Use toFixed, to solve this problem.

The toFixed() method sets the number of decimal places in float values. This method converts the number into a string, with the specified number of digits after the decimal. If no value is passed as a parameter, then it takes 0 as default value i.e. no decimal points are displayed.

Below is the working code snippet:

 (257.78 * 100).toFixed() // if decimal is not required
 (257.78 * 100).toFixed(2) // upto 2 decimal place
Rishu Ranjan
  • 494
  • 4
  • 7