How to deal with floating point number precision in JavaScript?

Viewed 600593

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.

47 Answers

decimal.js, big.js or bignumber.js can be used to avoid floating-point manipulation problems in Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimalist; easy-to-use; precision specified in decimal places; precision applied to division only.

bignumber.js: bases 2-64; configuration options; NaN; Infinity; precision specified in decimal places; precision applied to division only; base prefixes.

decimal.js: bases 2-64; configuration options; NaN; Infinity; non-integer powers, exp, ln, log; precision specified in significant digits; precision always applied; random numbers.

link to detailed comparisons

Elegant, Predictable, and Reusable

Let's deal with the problem in an elegant way reusable way. The following seven lines will let you access the floating point precision you desire on any number simply by appending .decimal to the end of the number, formula, or built in Math function.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Cheers!

Solved it by first making both numbers integers, executing the expression and afterwards dividing the result to get the decimal places back:

function evalMathematicalExpression(a, b, op) {
    const smallest = String(a < b ? a : b);
    const factor = smallest.length - smallest.indexOf('.');

    for (let i = 0; i < factor; i++) {
        b *= 10;
        a *= 10;
    }

    a = Math.round(a);
    b = Math.round(b);
    const m = 10 ** factor;
    switch (op) {
        case '+':
            return (a + b) / m;
        case '-':
            return (a - b) / m;
        case '*':
            return (a * b) / (m ** 2);
        case '/':
            return a / b;
    }

    throw `Unknown operator ${op}`;
}

Results for several operations (the excluded numbers are results from eval):

0.1 + 0.002   = 0.102 (0.10200000000000001)
53 + 1000     = 1053 (1053)
0.1 - 0.3     = -0.2 (-0.19999999999999998)
53 - -1000    = 1053 (1053)
0.3 * 0.0003  = 0.00009 (0.00008999999999999999)
100 * 25      = 2500 (2500)
0.9 / 0.03    = 30 (30.000000000000004)
100 / 50      = 2 (2)

From my point of view, the idea here is to round the fp number in order to have a nice/short default string representation.

The 53-bit significand precision gives from 15 to 17 significant decimal digits precision (2−53 ≈ 1.11 × 10−16). If a decimal string with at most 15 significant digits is converted to IEEE 754 double-precision representation, and then converted back to a decimal string with the same number of digits, the final result should match the original string. If an IEEE 754 double-precision number is converted to a decimal string with at least 17 significant digits, and then converted back to double-precision representation, the final result must match the original number.
...
With the 52 bits of the fraction (F) significand appearing in the memory format, the total precision is therefore 53 bits (approximately 16 decimal digits, 53 log10(2) ≈ 15.955). The bits are laid out as follows ... wikipedia

(0.1).toPrecision(100) ->
0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000

(0.1+0.2).toPrecision(100) ->
0.3000000000000000444089209850062616169452667236328125000000000000000000000000000000000000000000000000

Then, as far as I understand, we can round the value up to 15 digits to keep a nice string representation.

10**Math.floor(53 * Math.log10(2)) // 1e15

eg.

Math.round((0.2+0.1) * 1e15 ) / 1e15
0.3
(Math.round((0.2+0.1) * 1e15 ) / 1e15).toPrecision(100)
0.2999999999999999888977697537484345957636833190917968750000000000000000000000000000000000000000000000

The function would be:

function roundNumberToHaveANiceDefaultStringRepresentation(num) {

    const integerDigits = Math.floor(Math.log10(Math.abs(num))+1);
    const mult = 10**(15-integerDigits); // also consider integer digits
    return Math.round(num * mult) / mult;
}

Avoid dealing with floating points during the operation using Integers

As stated on the most voted answer until now, you can work with integers, that would mean to multiply all your factors by 10 for each decimal you are working with, and divide the result by the same number used.

For example, if you are working with 2 decimals, you multiply all your factors by 100 before doing the operation, and then divide the result by 100.

Here's an example, Result1 is the usual result, Result2 uses the solution:

var Factor1="1110.7";
var Factor2="2220.2";
var Result1=Number(Factor1)+Number(Factor2);
var Result2=((Number(Factor1)*100)+(Number(Factor2)*100))/100;
var Result3=(Number(parseFloat(Number(Factor1))+parseFloat(Number(Factor2))).toPrecision(2));
document.write("Result1: "+Result1+"<br>Result2: "+Result2+"<br>Result3: "+Result3);

The third result is to show what happens when using parseFloat instead, which created a conflict in our case.

I could not find a solution using the built in Number.EPSILON that's meant to help with this kind of problem, so here is my solution:

function round(value, precision) {
  const power = Math.pow(10, precision)
  return Math.round((value*power)+(Number.EPSILON*power)) / power
}

This uses the known smallest difference between 1 and the smallest floating point number greater than one to fix the EPSILON rounding error ending up just one EPSILON below the rounding up threshold.

Maximum precision is 15 for 64bit floating point and 6 for 32bit floating point. Your javascript is likely 64bit.

My answer could be late, but here is my solution:

function float(equation, precision = 9) {
    return Math.floor(equation * (10 ** precision)) / (10 ** precision);
}

console.log(float(0.1 * 0.2)); // => 0.02
console.log(float(0.2 + 0.4)); // => 0.6
console.log(float(1 / 3));     // => 0.333333333
console.log(float(1 / 3, 2));  // => 0.33

If you don't want to think about having to call functions each time, you can create a Class that handles conversion for you.

class Decimal {
  constructor(value = 0, scale = 4) {
    this.intervalValue = value;
    this.scale = scale;
  }

  get value() {
    return this.intervalValue;
  }

  set value(value) {
    this.intervalValue = Decimal.toDecimal(value, this.scale);
  }

  static toDecimal(val, scale) {
    const factor = 10 ** scale;
    return Math.round(val * factor) / factor;
  }
}

Usage:

const d = new Decimal(0, 4);
d.value = 0.1 + 0.2;              // 0.3
d.value = 0.3 - 0.2;              // 0.1
d.value = 0.1 + 0.2 - 0.3;        // 0
d.value = 5.551115123125783e-17;  // 0
d.value = 1 / 9;                  // 0.1111

Of course, when dealing with Decimal there are caveats:

d.value = 1/3 + 1/3 + 1/3;   // 1
d.value -= 1/3;              // 0.6667
d.value -= 1/3;              // 0.3334
d.value -= 1/3;              // 0.0001

You'd ideally want to use a high scale (like 12), and then convert it down when you need to present it or store it somewhere. Personally, I did experiment with creating a UInt8Array and trying to create a precision value (much like the SQL Decimal type), but since Javascript doesn't let you overload operators, it just gets a bit tedious not being able to use basic math operators (+, -, /, *) and using functions instead like add(), substract(), mult(). For my needs, it's not worth it.

But if you do need that level of precision and are willing to endure the use of functions for math, then I recommend the decimal.js library.

I was looking the same fix and I worked out that if you add a whole number in like 1 and evaluate that console.log(0.1 * 0.2 + 1);. Which results in 1.02. This can be used to round the original x variable to the correct amount.

Once the length of the decimal places 2 is retrieved in your example we can then use it with the toFixed() function to round the original x variable correctly.

See inside the code as to what this function does in the commented sections.

var myX= 0.2 * 0.1;
var myX= 42.5-42.65;
var myX= 123+333+3.33+33333.3+333+333;

console.log(myX);
// Outputs (example 1): 0.020000000000000004
// Outputs (example 2): -0.14999999999999858
// Outputs (example 3): 34458.630000000005
// Wrong

function fixRoundingError(x) {
// 1. Rounds to the nearest 10
//    Also adds 1 to round of the value in some other cases, original x variable will be used later on to get the corrected result.
var xRound = eval(x.toFixed(10)) + 1;
// 2. Using regular expression, remove all digits up until the decimal place of the corrected equation is evaluated..
var xDec = xRound.toString().replace(/\d+\.+/gm,'');
// 3. Gets the length of the decimal places.
var xDecLen = xDec.length;
// 4. Uses the original x variable along with the decimal length to fix the rounding issue.
var x = eval(x).toFixed(xDecLen);
// 5. Evaluate the new x variable to remove any unwanted trailing 0's should there be any.
return eval(x);
}

console.log(fixRoundingError(myX));
// Outputs (example 1): 0.02
// Outputs (example 2): -0.15
// Outputs (example 3): 34458.63
// Correct

It returns the same value as the calculator in windows in every case I've tried and also rounds of the result should there be any trailing 0's automatically.

I like the approach with correction factor and here it is my shortened decisions for both ES6 and ES5 standards. Its advantage compared to the toFixed method is that it does not leave unnecessary zeros in the end ot the number, if we want to round to hundreds, but the result number is some tenth number:

ES6 variant:

// .1 + .2
((a,b,crr) => (a*crr + b*crr)/crr)(.1,.2,100/*correction factor*/);//0.3
// .1 * .2
((a,b,crr) => a*crr*b/crr)(.1,.2,100);//0.02

ES5 variant:

// .1 + .2
(function(a,b,crr){ return (a*crr + b*crr)/crr; })(.1,.2,100/*correction factor*/);//0.3
// .1 * .2
(function(a,b,crr){ return a*crr*b/crr; })(.1,.2,100);//0.02

I usually use something like this.

function pf(n) {
    return Math.round(n * 1e15) / 1e15;
}

I make no claim that this is optimal in any way, but I like it for its simplicity. It rounds the number off to 15 decimal places or so. I have not witnessed it returning inaccurate floats, though what is odd is that it has done so when I use * 1e-15 at the end, but not with this method.

This solution may be better suited for casual use -- and not precise mathematical use -- where precision errors are messing up your code.

If you need to make arbitrary-precision floating-point computations, you can use my NPM library called gmp-wasm, which is based on GMP + MPFR libraries. You can easily set any precision you want, and return the result with fixed precision.

<script src="https://cdn.jsdelivr.net/npm/gmp-wasm"></script>
<script>
  gmp.init().then(({ getContext }) => {
    const ctx = getContext({ precisionBits: 100 });
    const result = ctx.Float('0.1').mul(ctx.Float('0.2'));
    document.write(`0.1 * 0.2 = ` + result.toFixed(2));
    ctx.destroy();
  });
</script>

This npm library is built to resolve this, from my own use case, and has been deployed in large scale production. Hope it would be of help to others.

npm i jsbi-calculator

It is based on the GoogleChromeLabs/jsbi project and uses stringified expressions to perform arbitrary rational computation, ie11-compatible.

Usage for browser:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Jsbi-calculator Test</title>
    <script src="https://cdn.jsdelivr.net/npm/jsbi-calculator/dist/jsbi-calculator-umd.js"></script>
  </head>
  <body></body>
  <script type="text/javascript">
    const expressionOne = "((10 * (24 / ((9 + 3) * (-2)))) + 17) + 5.2";
    const resultOne = JBC.calculator(expressionOne);
    console.log(resultOne);
    // -> '12.2'

    const userAgent = navigator.userAgent;
    const isIE11 =
      userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;
    let max;
    // MAX_SAFE_INTEGER not available in IE11
    max = isIE11 ? "9007199254740991" : String(Number.MAX_SAFE_INTEGER);

    console.log(max);
    // -> '9007199254740991'
    const expressionTwo = max + " + 2.2";
    const resultTwo = JBC.calculator(expressionTwo);
    console.log(resultTwo);
    // -> '9007199254740993.2'
  </script>
</html>

Threw this together based on @SheetJs's answer, which I liked:

  getCorrectionFactor(numberToCheck: number): number {
    var correctionFactor: number = 1;
    
    if (!Number.isInteger(numberToCheck)) {
      while (!Number.isInteger(numberToCheck)) {
        correctionFactor *= 10;
        numberToCheck *= correctionFactor;
      }
    }

    return correctionFactor;
  }

Related