前端知识点

js浮点数精度计算问题

2019-10-20  本文已影响0人  AmazRan

前言

项目中,总是不可避免会出现js的浮点数精度计算问题。
抛出经典问题:为什么0.1===0.10.1+0.2!==0.3


出现问题的原因

根源要追溯到JavaScript的浮点数的存储上。和其它语言如Java和Python不同,JavaScript中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数(相关的还有float 32位单精度)。
这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。64位比特又可分为三个部分:

在了解了存储的原理之后可能还不太理解。通过案例应该可以更加具体一点。
在执行计算0.1+0.2的过程中发生了什么?
因为计算机内部只能通过二级制处理数据,0.1和0.2会先被转换成二进制,但是由于浮点数用二进制表示时是无穷的,因此会被转换为:

0.1 -> 0.0001 1001 1001 1001...(1001循环)
0.2 -> 0.0011 0011 0011 0011...(0011循环)

相加之后就是0.0100 1100 ...(1100循环)
前面提到了,JavaScript提供的有效数字最长为53个二进制位(64位浮点的后52位+有效数字第一位的1)。最后的 52 位尾数超出部分发生了截断,因此,0.1+0.2的计算结果在截断后,二进制转换回十进制,得到的结果是0.30000000000000004


整数精度问题

前面提到了,既然小数存在,那整数也存在着上限。
js的Number自带存储了最大/最小安全整数
Number.MAX_SAFE_INTEGER === 9007199254740991(2^53 - 1)
Number.MIN_SAFE_INTEGER === -9007199254740991-(2^53 - 1)

至于为什么不是52次方-1。对于二进制来说, 小数点前保留一位, 规格化后始终是 1.***, 节省了 1 bit,这个 1 并不需要保存,所以可以多展示1位数,即2^53-1。

那么超出之后进行运算会发生什么就不用再解释了,原理同浮点数是一样的。


解决方案

除了自己造轮子的运算中进位,更推荐使用Math.js、decimal.js、big.js等类库,其中有完善的解决方案,具体想了解实现原理可以移步官方git直接查看函数方法。

不过通常不需要引入整个库,只需要其中的几个方法就适用了,例如常用的保留小数后几位(基于parseFloat和tofixed去改造……)


心得体会

前端的浮点数问题并不会太复杂,因为涉及到业务数据的计算通常都是交给后端完成,但是精确位数尤其是金额的展示还是不能出错。虽然涉及到计算机组成原理的知识,其实还是很容易弄懂的。


参考

JS中浮点数精度问题

上一篇下一篇

猜你喜欢

热点阅读