JS不止小数,大数也会精度丢失

4,519 阅读2分钟

问题

首先JS里面0.1 + 0.2 !== 0.3已经算是常识了,因为不只是JS,Java和其他语言中也存在类似的问题,就是浮点数精度不准确问题。

但是由于我们项目的特殊情况,需要使用超过16位的数字时发现,JS竟然大数也会丢失精度。

比如说console.log(123456789012345678901)的结果竟然是123456789012345680000

这就让人非常不理解。因为好像数字溢出时变为Infinity/NaN/抛错/直接变为某个最大固定值才更符合直觉。

原因

这个原因主要是JS采用的IEEE754规范规定了double型(注意JS中不存在int/float概念,默认都是double)一共64位,其中1位符号位,11位表示指数E,剩下52位为有效数字

这也就导致JS的数字只有在[-(Math.pow(2,53)-1), Math.pow(2,53)-1]内的数字才是符合IEEE754规范的。

所以JS(ES6+)也提供了最大/最小安全数Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER, 以及方法Number.isSafeInteger()以供对比。

注意!

位运算 | & ~ ^ 和移位运算 >> << >>> <<<都是基于32位整数的,所以涉及到位运算时,请注意不要超过Math.pow(2,31)-1也就是2147483647

(真正转变为Infinity的数其实也是有的,就是大于等于Math.pow(2,1024)

解决方式

首先JS的标准是不能够去改变的,那么现在说一下常见的解决思路

纯数字的id/key/订单编号等

改用string类型,前后端传值时进行约定

巨大的金额,比如津巴布韦币 10万亿津元兑换2美分

建议根据地区使用不同的基础单位,如果金额巨大,基本单位可为万/千等,或者对位数进行拆分

ES10引入BigInt

BigInt 是ES10新引入的内建对象,可创建超过(Math.pow(2,53)-1)的大数,表现为在数字后面多加一个n,就像123456789012345678901n