关于js中number类型安全范围的理解

1,955 阅读4分钟

js中number类型的安全范围

  • 前几天在工作中遇到了一个问题,发现我们一个列表页个别数据点击详情时会报错,经过日志查看发现是由于传入的id有误,但是我们的页面上并没有对数据做更改的操作为什么id会改变呢?而且还只是部分数据会改变。在chorme的debug界面看了很久也没找到问题,直到偶然点开了network下的preview界面,突然发现这里面id已经发生了变化,即response和preview里面id的值不一样了!为什么会这样呢,感觉可能是js中number类型的问题 当时就查了下js的number类型,发现js的number类型最大可表示10^308 同时它看到还有一个安全范围2^53。

什么是安全范围呢

这里安全范围的意思是指能够准确区分两个不相同的值,例如 100 !== 100 +1 。

但是Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 将得到 true的结果

js中number类型的实现原理

由于js的数值类型的都是采用IEEE 754标准定义的64位双精度浮点格式来实现(java中double类型也采用这个标准进行实现)

IEEE 754标准定义的64位双精度浮点格式的相关资料如下:

IEEE 754百度百科

IEEE 754表示浮点数

js中number类型的数字也是用8字节来表示的
将数字正规化表示后可以表示为:

±1.bbbb...x2^P   b为尾数位 p位指数位
IEEE 754标准中定义8字节表示浮点数中符号位为1位 尾数位为52位 指数位为11位

其中第1位是符号位,第2到12位表示指数位,第13-64位表示尾数位

下面是一个例子:3.14在双精度中表示为
0  1000-0000-000  0010-0011-1101-0111-0000-1010-0011-1101-0111-0000-1010-0011-1101-1b
其中第1是符号位 0表示为正数 第二位到第12位表示指数位(3 转成2进制为11b 所以其指数位是1
在加上移码(2^10-1)后表示为1000-0000-000 剩余的52位表示小数转化为二进制后的表示
为0010-0011-1101-0111-0000-1010-0011-1101-0111-0000-1010-0011-1101-1
由于52位以后的数字会被舍弃所以number 能精确表示的整数范围是2^53 (52位尾数位加整数位的1)

在线进制转换工具

浮点数舍入规则

以52位尾数位的双精度浮点数为例,舍入时需要重点参考第53位:

若第53位为1,而第53位之后全部为0。此时就要使第52位为0:
若第52位本来就是0则不管,若第52位为1,则第53位就要向第52位进一位,这样第52位就可以为0
若不是上面的情况,即第53位1,但是第53位之后不全为0,则第53位就要向第52位进一完成上舍入。
若也不是上面两种情况,那么第53位必为0,此时直接舍去不进位,称为下舍入。

所以当小数部分超过52位后的数字会被截断,就会发生精度缺失,无法再去精确地表示这个数字 使得原来不相等的数字在这种情况下变得相等,因为js中是使用浮点数模拟的整数,所以其能精确表示的数字范围最多只能是小数点后52位

解决精度缺失的方法

方法一:

当需要使用较大的数字类型时,可以使用字符串来代替,字符串不会存在精度缺失的问题。

方法二:

如果不使用字符串代替也可以使用BigInt来代替,但bigInt 现在尚在ECMAScript 标准化过程中的 第三阶段还没有成为标准因此只有部分浏览器实现了此功能。

参考资料:

移码

number类型MDN文档

js新primitive类型bigInt

十进制转二进制方法