思考:
为什么 0.1+0.2 != 0.3?
为什么 1.005.toFixed (2)=1.00 而不是 1.01
双精度存储
首先在开始之前需要了解一下 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
为什么 0.1+0.2 != 0.3?
答:0.1 转二进制 =====> 0.0001100110011001100…(1100 循环)
转科学计数法 =====> 1.100110011…(1100 循环) *2^-4
数据是无限循环的,但是可供使用的尾数位却是有限的,只有 52 位可以使用,所以在第 53 位会被舍去并且进位
计算结果转换为十进制数字就是 0.30000000000000004
所以就是因为,0.1 和 0.2 在计算机中的二进制存储会让它们本身损失掉一定的精度,而它们在计算机中的二进制存储转换成十进制时已经不是真正的 0.1 和 0.2 了,相加的结果也就自然不是 0.3 了。
问题来了,既然 0.1 在计算机中的存储已经有了舍入误差,那为什么 num=0.1 能得到 0.1 呢?
可以在控制台使用 toPrecision 看一下 0.1 在不同精度下的返回
可以看出来其实 0.1 是截断了一部分精度后得到的结果,那么这个问题就可以转化为:双精度浮点数是按什么规则来截断的呢?
答:如果一个 IEEE 754 的双精度浮点数被转成至少含 17 位有效数字的十进制数字字符串,当这个字符串转回双精度浮点数时,必须要跟原来的数相同;换句话说,如果一个双精度的浮点数转为十进制的数字时,只要它转回来的双精度浮点数不变,精度取最短的那个就行。
拿 0.1 来举例子,0.1 和 0.10000000000000001 转成双精度浮点数的存储是一样的,所以取最短的 0.1 就行了。
为什么 1.005.toFixed (2)=1.00 而不是 1.01
因为在第一个问题中已经说了,一个十进制数字转为双精度浮点数然后再取出来时,跟原十进制数字可能会有误差,试一下 1.005 取 20 个精度:
1.005.toPrecision(20) // 返回 1.0049999999999998934
很明显 1.005 只是一个被截断后的数字,它的双精度浮点数代表的 20 位精度的数字是 1.0049999999999998934,所以进行保留 2 位的四舍五入时,2 位后的数字会被全部舍去。