几个问题
先抛出几个问题
- 为什么0.1+0.2 != 0.3?
-
为什么1.005.toFixed(2)=1.00而不是1.01
-
为什么会有Number.MAX_VALUE和Number.MAX_SAFE_INTEGER这两个常量同时存在?
接下来就以这三个问题为目的来梳理一下来龙去脉。
双精度存储
首先在开始之前需要了解一下JavaScript的number类型在计算机中是如何存储的,这也是一切问题的基础。JavaScript的数字都是number类型的,不管是整数还是浮点数都以IEEE754双精度的格式存储在计算机中,什么是双精度呢?就是以64个bit位来存储,具体的存储格式是:
分别是1个符号位+11个指数位+52个尾数位
举个例子,如果是5.5这个数字的话,则计算过程是这样的:
5.5 转二进制 =====> 101.1 科学计数法 =====> 1.011*2^2
存入计算机:
符号位:0
指数位:2 加1023 =====> 1025 转二进制 =====> 10000000001
尾数位:1.011 隐去小数点左边的1 =====> 011
存入计算机,如下图,截图来自IEEE754可视化,感兴趣可以把玩一下
为什么0.1+0.2 != 0.3?
在浏览器控制台可以测试一下结果:
0.1 转二进制 =====> 0.0001100110011001100...(1100循环)
转科学计数法 =====> 1.100110011...(1100循环) *2^-4
数据是无限循环的,但是可供使用的尾数位却是有限的,只有52位可以使用,所以在第53位会被舍去并且进位
最终在计算机中存储如下图:
类似的0.2在计算机中的存储如下图:
所以最终的计算就是:
0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111
问题来了,既然0.1在计算机中的存储已经有了舍入误差,那为什么num=0.1能得到0.1呢?
可以看出来其实0.1是截断了一部分精度后得到的结果,那么这个问题就可以转化为:双精度浮点数是按什么规则来截断的呢?
在双精度浮点数的英文wiki中可以找到中可以找到这么一段话:
大意是:如果一个 IEEE 754 的双精度浮点数被转成至少含17位有效数字的十进制数字字符串,当这个字符串转回双精度浮点数时,必须要跟原来的数相同;换句话说,如果一个双精度的浮点数转为十进制的数字时,只要它转回来的双精度浮点数不变,精度取最短的那个就行。
为什么1.005.toFixed(2)=1.00而不是1.01
很明显1.005只是一个被截断后的数字,它的双精度浮点数代表的20位精度的数字是1.0049999999999998934,所以进行保留2位的四舍五入时,2位后的数字会被全部舍去。
为什么会有Number.MAX_VALUE和Number.MAX_SAFE_INTEGER这两个常量同时存在?
为什么最大安全整数是2^53-1?前面说到了JavaScript浮点数存储是52位尾数位,但是因为科学计数法小数点左侧的1会在存储时省去,所以52位尾数+省去的1位=53个可表示的位数。
哪为什么2^53-1是最大安全整数呢?比它大会怎样?
2^53 转二进制 =====> 100000000000000000000000000000000000000000000000000000(53个0)
转为科学计数法 =====> 1.00000000000000000000000000000000000000000000000000000(53个0)*2^53
存入计算机 =====> 尾数位只有52位所以截掉末尾的0只能存52个0
2^53+1 转二进制 =====> 100000000000000000000000000000000000000000000000000001(52个0)
转为科学计数法 =====> 1.00000000000000000000000000000000000000000000000000001(52个0)
存入计算机 =====> 尾数位只有52位所以截掉末尾的1只能存52个0