精度问题汇总
想用无限的位来示意无穷的数字,显然是不可能的,因而会呈现一些列精度问题:
浮点数精度问题,比方 0.1 + 0.2 !== 0.3
大数精度问题,比方 9999 9999 9999 9999 == 1000 0000 0000 0000 1
toFixed 四舍五入后果不精确,比方 1.335.toFixed(2) == 1.33
浮点数精度和 toFixed 其实属于同一类问题,都是因为浮点数无奈准确示意引起的,如下:
(1.335).toPrecision(20); // “1.3349999999999999645”
而对于大数精度问题,咱们能够先看上面这个游戏代码片段:
// 能准确示意的整数范畴下限,S 为 1 个 0,E 为 11 个 0,S 为 53 个 1
Math.pow(2, 53) – 1 === Number.MAX_SAFE_INTEGER // true
// 能准确示意的整数范畴上限,S 为 1 个 1,E 为 11 个 0,S 为 53 个 1
-(Math.pow(2, 53) – 1) === Number.MIN_SAFE_INTEGER // true
// 能示意的最大数字,S 为 1 个 0,E 为 971,S 为 53 个 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
通过以上能够明确,[MIN_SAFE_INTEGER, MAX_SAFE_INTEGER] 的整数都能够准确示意,然而超出这个 www.sangpi.com 范畴的整数就不肯定能准确示意。这样就会产生所谓的大数精度失落问题。
解决思路
首先思考的是如何解决浮点数运算的精度问题,有 3 种思路:
思考到每次浮点数运算的偏差十分小 (其实不然),能够对后果进行指定精度的四舍五入,比方能够 parseFloat(result.toFixed(12));
将浮点数转为整数运算,再对后果做除法。比方 0.1 + 0.2,能够转化为 (1*2)/3。
把浮点数转化为字符串,模仿理论运算的过程。
先来看第一种计划,在大多数状况下,它能够失去正确后果,然而对一些极其状况,toFixed 到 12 是不够的,比方:
210000 10000 1000 * 8.2 // 17219999999999.998
parseFloat(17219999999999.998.toFixed(12)); // 17219999999999.998,而正确后果为 17220000000000
下面的状况,如果想让后果正确,须要 toFixed(2),这显然是不可承受的。
再看第二种计划,比方 number-precision 这个库就是应用的这种计划,然而这也是有问题的,比方:
// 这两个浮点数,转化为整数之后,相乘的后果曾经超过了 MAX_SAFE_INTEGER
123456.789 123456.789 // 转化为 (123456789 123456789)/1000000,后果是 15241578750.19052
所以,最终思考应用第三种计划,目前曾经有了很多较为成熟的库,比方 bignumber.js,decimal.js,以及 big.js 等。咱们能够依据本人的需要来抉择对应的工具。并且,这些库不仅解决了浮点数的运算精度问题,还反对了大数运算,并且修复了原生 toFixed 后果不精确的问题。
题外话
还有另外一个与 JavaScript 计算相干的问题,即 Math.round(x),它尽管不会产生精度问题,然而它有一点小陷阱容易疏忽。上面是它的舍入的策略:
如果小数局部大于 0.5,则舍入到下一个绝对值更大的整数。
如果小数局部小于 0.5,则舍入到下一个绝对值更小的整数。
如果小数局部等于 0.5,则舍入到下一个正无穷方向上的整数。
所以,对 Math.round(-1.5),其后果为 -1,这可能不是咱们想要的后果。
当然,下面提到的 big.js 等库,都提供了本人的 round 函数,并且能够指定舍入规定,以防止这个问题。