Easy Tutorial
❮ Open Source License Programming Intro ❯

Precision Issues and Solutions in JavaScript

Category Programming Techniques

Numbers in JavaScript are represented using the 64-bit double-precision floating-point format as per the IEEE 754 standard. The sign bit S, exponent bit E, and significand bit M occupy 1, 11, and 52 bits respectively, and the ES5 specification indicates that the exponent bit E's value range is [-1074, 971].


Summary of Precision Issues

It is obviously impossible to represent infinite numbers with a finite number of bits, hence a series of precision issues arise:

Floating-point precision and toFixed actually belong to the same category of issues, both caused by the inability of floating-point numbers to be represented precisely, as follows:

(1.335).toPrecision(20);    // "1.3349999999999999645"

Regarding the large number precision issue, we can first look at the following code snippet:

// The upper limit of the integer range that can be accurately represented, with S as 1 zero, E as 11 zeros, and S as 52 ones
Math.pow(2, 53) - 1 === Number.MAX_SAFE_INTEGER    // true
// The lower limit of the integer range that can be accurately represented, with S as 1 one, E as 11 zeros, and S as 52 ones
-(Math.pow(2, 53) - 1) === Number.MIN_SAFE_INTEGER    // true
// The maximum number that can be represented, with S as 1 zero, E as 971, and S as 52 ones
(Math.pow(2, 53) - 1) * Math.pow(2, 971) === Number.MAX_VALUE    // true
// The closest positive number to 0 that can be represented, with S as 1 zero, E as -1074, and S as 0
Math.pow(2, -1074) === Number.MIN_VALUE // true

From the above, it is clear that integers within the range [MIN_SAFE_INTEGER, MAX_SAFE_INTEGER] can all be accurately represented, but integers beyond this range may not be accurately representable. This leads to the so-called large number precision loss issue.

Solution Approaches

The first consideration is how to solve the precision issues of floating-point arithmetic, with 3 approaches:

Let's first look at the first solution. In most cases, it can yield the correct result, but for some extreme cases, toFixed to 12 is not enough, such as:

210000 * 10000  * 1000 * 8.2    // 17219999999999.998
parseFloat(17219999999999.998.toFixed(12));    // 17219999999999.998, while the correct result is 17220000000000

In the above case, if you want the correct result, you need toFixed(2), which is obviously unacceptable.

Looking at the second solution, for example, the library number-precision uses this approach, but it also has problems, such as:

// These two floating-point numbers, when converted to integers, the result of multiplication exceeds MAX_SAFE_INTEGER
123456.789 * 123456.789     // Converted to (123456789 * 123456789)/1000000, the result is 15241578750.19052

So, the third solution is ultimately considered, and there are already many mature libraries available, such as bignumber.js, decimal.js, and big.js, etc

❮ Open Source License Programming Intro ❯