Numbers in JavaScript are represented using 64-bit double-precision floating-point format according to the IEEE 754 standard. Among them, the sign bit S, exponent bits E, and mantissa bits M occupy 1, 11, and 52 bits respectively, and in theES5 specificationit is pointed out that the value range of exponent bits E is[-1074, 971]。


Summary of Precision Issues

Obviously, it is impossible to represent infinite numbers with finite bits, so a series of precision issues arise:

  • Floating-point precision issues, such as0.1 + 0.2 !== 0.3
  • Large number precision issues, such as9999 9999 9999 9999 == 1000 0000 0000 0000 1
  • Inaccurate toFixed rounding results, such as1.335.toFixed(2) == 1.33

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

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

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

// 能精确表示的整数范围上限,S为1个0,E为11个0,S为52个1
Math.pow(2, 53) - 1 === Number.MAX_SAFE_INTEGER    // true
// 能精确表示的整数范围下限,S为1个1,E为11个0,S为52个1
-(Math.pow(2, 53) - 1) === Number.MIN_SAFE_INTEGER    // true
// 能表示的最大数字,S为1个0,E为971,S为52个1
(Math.pow(2, 53) - 1) * Math.pow(2, 971) === Number.MAX_VALUE    // true
// 能表示的最接近于0的正数,S为1个0,E为-1074,S为0
Math.pow(2, -1074) === Number.MIN_VALUE // true

From the above, we can understand that[MIN_SAFE_INTEGER, MAX_SAFE_INTEGER]integers within this range can be precisely represented, but integers beyond this range may not necessarily be precisely represented. This results in the so-called large number precision loss problem.

Solution Approaches

First, consider how to solve the precision issue of floating-point arithmetic. There are 3 approaches:

  • Considering that the deviation of each floating-point operation is very small (actually it is not), you can round the result to a specified precision, for example, you canparseFloat(result.toFixed(12));
  • Convert floating-point numbers to integers for calculation, and then divide the result. For example, 0.1 + 0.2 can be converted to(1*2)/3。
  • Convert floating-point numbers to strings and simulate the actual calculation process.

Let's first look at the first approach. In most cases, it can produce correct results, but for some extreme cases, toFixed to 12 digits is not enough, for example:

210000 * 10000  * 1000 * 8.2    // 17219999999999.998
parseFloat(17219999999999.998.toFixed(12));    // 17219999999999.998,而正确结果为 17220000000000

For the above situations, if you want the result to be correct, you needtoFixed(2), which is obviously unacceptable.

Now look at the second approach, for examplenumber-precisionThis library uses this approach, but it also has problems, for example:

// 这两个浮点数,转化为整数之后,相乘的结果已经超过了 MAX_SAFE_INTEGER
123456.789 * 123456.789     // 转化为 (123456789 * 123456789)/1000000,结果是 15241578750.19052

Therefore, in the end, we consider using the third approach. At present, there are already many mature libraries, such asbignumber.js,decimal.js, andbig.jsetc. We can choose the corresponding tools according to our needs. Moreover, these libraries not only solve the precision issues of floating-point arithmetic, but also support large number operations and fix the inaccurate native toFixed results.


Aside

There is another issue related to JavaScript calculation, namelyMath.round(x), although it does not cause precision issues, it has a small pitfall that is easy to overlook. The following is its rounding strategy:

  • If the fractional part is greater than 0.5, round to the next integer with a larger absolute value.
  • If the fractional part is less than 0.5, round to the next integer with a smaller absolute value.
  • If the fractional part equals 0.5, thenround to the next integer in the direction of positive infinity。

Therefore, for Math.round(-1.5), the result is -1, which may not be the result we want.

Of course, the big.js and other libraries mentioned above all provide their own round functions, and allow specifying rounding rules to avoid this problem.

Original address: https://xwjgo.github.io/2018/03/17/js%E4%B8%AD%E7%B2%BE%E5%BA%A6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/