Categories
floating-point javascript

How to deal with floating point number precision in JavaScript?

769

I have the following dummy test script:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

This will print the result 0.020000000000000004 while it should just print 0.02 (if you use your calculator). As far as I understood this is due to errors in the floating point multiplication precision.

Does anyone have a good solution so that in such case I get the correct result 0.02? I know there are functions like toFixed or rounding would be another possibility, but I’d like to really have the whole number printed without any cutting and rounding. Just wanted to know if one of you has some nice, elegant solution.

Of course, otherwise I’ll round to some 10 digits or so.

14

  • 145

    Actually, the error is because there is no way to map 0.1 to a finite binary floating point number.

    Sep 22, 2009 at 8:14

  • 14

    Most fractions can’t be converted to a decimal with exact precision. A good explanation is here: docs.python.org/release/2.5.1/tut/node16.html

    Jan 10, 2011 at 18:35

  • 8

    possible duplicate of Is JavaScript’s Math broken?

    Nov 2, 2011 at 3:18

  • 58

    @SalmanA: That your JavaScript runtime hides this problem from you doesn’t mean I’m wrong.

    Nov 19, 2012 at 10:45

  • 9

    Disagree with Aaron, there are ways to code 0.1 perfectly and completely in binary. But IEEE 754 does not necessarily defines this. Imagine a representation where you would code the integer part in binary on the one hand, the decimal part on the other hand, up to n decimals, in binary too, like a normal integer > 0, and finally, the position of the decimal point. Well, you would represent 0.1 perfectly, with no error. Btw, since JS uses a finite number of decimals internally, they devs might as well coded the guts to not make that mistake on the last decimals.

    Nov 14, 2016 at 5:59


560

+25

From the Floating-Point Guide:

What can I do to avoid this problem?

That depends on what kind of
calculations you’re doing.

  • If you really need your results to add up exactly, especially when you
    work with money: use a special decimal
    datatype.
  • If you just don’t want to see all those extra decimal places: simply
    format your result rounded to a fixed
    number of decimal places when
    displaying it.
  • If you have no decimal datatype available, an alternative is to work
    with integers, e.g. do money
    calculations entirely in cents. But
    this is more work and has some
    drawbacks.

Note that the first point only applies if you really need specific precise decimal behaviour. Most people don’t need that, they’re just irritated that their programs don’t work correctly with numbers like 1/10 without realizing that they wouldn’t even blink at the same error if it occurred with 1/3.

If the first point really applies to you, use BigDecimal for JavaScript, which is not elegant at all, but actually solves the problem rather than providing an imperfect workaround.

21

  • 12

    I noticed your dead link for BigDecimal and while looking for a mirror, I found an alternative called BigNumber: jsfromhell.com/classes/bignumber

    – Jacksonkr

    Dec 1, 2011 at 4:52

  • 5

    @bass-t: Yes, but floats can exactly represent integers up to the length of the significand, and as per ECMA standard it’s a 64bit float. So it can exactly represent integers up to 2^52

    Jul 25, 2012 at 13:15

  • 8

    @Karl: The decimal fraction 1/10 cannot be represented as a finite binary fraction in base 2, and that’s what Javascript numbers are. So it is in fact exactly the same problem.

    Dec 23, 2014 at 15:44


  • 22

    I learned today that even integers have precision problems in javascript. Consider that console.log(9332654729891549) actually prints 9332654729891548 (ie off by one!)

    – mlathe

    Jul 10, 2015 at 21:01

  • 21

    @mlathe: Doh.. ;P… Between 2⁵²=4,503,599,627,370,496 and 2⁵³=9,007,199,254,740,992 the representable numbers are exactly the integers. For the next range, from 2⁵³ to 2⁵⁴, everything is multiplied by 2, so the representable numbers are the even ones, etc. Conversely, for the previous range from 2⁵¹ to 2⁵², the spacing is 0.5, etc. This is due to simply increasing|decreasing the base|radix 2|binary exponent in/of the 64-bit float value (which in turn explains the rarely documented ‘unexpected’ behavior of toPrecision() for values between 0 and 1).

    – GitaarLAB

    Mar 2, 2016 at 19:42


174

I like Pedro Ladaria’s solution and use something similar.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

Unlike Pedros solution this will round up 0.999…repeating and is accurate to plus/minus one on the least significant digit.

Note: When dealing with 32 or 64 bit floats, you should use toPrecision(7) and toPrecision(15) for best results. See this question for info as to why.

11

  • 31

    Any reason why you picked 12?

    – qwertymk

    Dec 27, 2015 at 1:09

  • 27

    toPrecision returns a string instead of a number. This might not always be desirable.

    – SStanley

    Mar 13, 2016 at 23:42

  • 9

    parseFloat(1.005).toPrecision(3) => 1.00

    – Peter

    May 27, 2016 at 11:27

  • 5

    @user2428118, I know, I meant to show the rounding error, The outcome is 1.00 instead of 1.01

    – Peter

    Jul 9, 2016 at 7:39


  • 14

    What @user2428118 said may not be obvious enough: (9.99*5).toPrecision(2) = 50 instead of 49.95 because toPrecision counts the whole number, not just decimals. You can then use toPrecision(4), but if your result is >100 then you’re out of luck again, because it’ll allow the first three numbers and one decimal, that way shifting the dot, and rendering this more or less unusable. I ended up using toFixed(2) instead

    – mehov

    Nov 15, 2018 at 11:38


90

For the mathematically inclined: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

The recommended approach is to use correction factors (multiply by a suitable power of 10 so that the arithmetic happens between integers). For example, in the case of 0.1 * 0.2, the correction factor is 10, and you are performing the calculation:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

A (very quick) solution looks something like:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

In this case:

> Math.m(0.1, 0.2)
0.02

I definitely recommend using a tested library like SinfulJS

7

  • 2

    Il love this elegant workaround but seems not to be perfect: jsfiddle.net/Dm6F5/1 Math.a(76.65, 38.45) returns 115.10000000000002

    Apr 16, 2014 at 12:29


  • 4

    Math.m(10,2332226616) is giving me “-19627406800” which is a negative value… I hope there must be a upper limit – might be that is causing this issue. Please suggest

    Jul 15, 2014 at 6:07


  • 2

    This all looks great, but seems to have a mistake or two in there somewhere.

    – MrYellow

    Mar 12, 2015 at 2:50

  • 10

    Very quick solution he said…broken fix no one ever said.

    – Cozzbie

    Dec 11, 2015 at 13:27

  • 3

    Don’t use the above code. It’s absolutely not a ‘quick solution’ if it does not work. This is a math related question, so accuracy is required.

    – Drenai

    Jun 9, 2017 at 13:55