为什么0.1+0.2 != 0.3?
console.log(0.1+0.2==0.3)//false
console.log(0.1+0.2)//0.30000000000000004
太长不看版
运算结果不相等原因:
-
0.1和0.2在二进制下都是无限循环小数;
-
在精度发生损失时,进行了“0舍1入”中的1入,即0.1和0.2在计算机内的存储形式都比它实际的二进制表示要大一点,最后0.3还发生了一次“1入”。(一共四次)
-
这不是JS独有的问题,它是由于计算机数据存储方式导致的。
数据存储
计算机中的数据是以二进制的形式储存,对小数来说,影响就是0.1没有办法以“原子”形式存在
十进制中,0.1是1除以10;但二进制中的“原子”只能是1除以2(除以它的进制),或者再除以2。即二进制只能完整表示0.5 0.25 0.125 0.0625...0.1只能用这些数来近似模拟
JS中的数据存储
Javascript中数值数据类型只有number唯一一种,其存储方式是双精度浮点型小数(double)(无论是16位机、32位机还是64位机中都是占64位)
浮点数的存在是为了解决非纯整数/非纯小数的表示(纯整数小数点在数字的末尾,纯小数小数点在数字的最首,而浮点数是在数字的中间部分,故称为“浮”)
那么一个浮点数的表示可以拆成两部分看,第一部分是不看小数点的数值,另一部分是小数点的位置(在后文中被称为阶码)。
一个占64位的双精度浮点数,不同位表示意义如下:
将0.1转码成二进制:
- 在计算机中存储的表示是:
1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010×2^-4 - 0.1实际存储是:
0(符号位)011 1111 1011(阶码位-4+1023)1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010(数值位,第一个1隐藏)
注意:舍入规则——当精度出现损失时,0舍1入。后面应该继续是1001...发生舍入,加一,最后的1001变成1010了
将0.2转码成二进制:
- 在计算机中存储的表示是:
1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010×2^-3 - 0.2实际存储方式是:
0(符号位)011 1111 1010(阶码位-3+1023)1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010(数值绝对值,第一个1省略)
可以看到一个问题,因为数值位的位数只有52位,存储的数据精度是有限的。但0.1和0.2在二进制下的表示都是无限循环小数。
0.1+0.2的运算过程:
对阶:小阶向大阶对齐,0.1的表示要改变(又发生了舍入)
0.1 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101×2^-3
计算:10.0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0111×2^-3(0.3)=
1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010×2^-3(0.2)+0.1 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101×2^-3(0.1)
规格化:
1. 00110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0111×2^-2(0.3)
舍入:
1. 00110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 100(舍去的是0,直接舍去,如果舍去的是1,在末尾加一)
最后计算得出的0.3转化为10进制
0.010011001100110011001100110011001100110011001100110100
可以看出,因为数据的存储形式和位数的限制,才导致了这样的误差,但这种误差不是必然,比如计算0.25+0.0625
console.log(0.25+0.0625)//0.3125
结果是精确的,因为0.25和0.625都可以在二进制下有限表示。
IEEE 754标准
在文章的第一张图中提到,阶码不能全为0,也不能全为1,所以范围是1到2046(七个0是0,七个1是2047)。
这是IEEE制定的浮点数标准,全0 和全1的阶码有特殊的含义。
将阶码部分标记E,数值位标记为M,第一个符号位标记为S。
- 在阶码正常范围(1到2046)时,真值是
(-1)^s×1.M×2^E-1023 - 当阶码全0时(E=0),如果M也是0,无论符号位,真值是0
- 当阶码全0时(E=0),如果M不是0,真值不是
(-1)^s×1.M×2^-1023,而是(-1)^s×0.M×2^-1022(记下来就好,此时是非规格化的) - 当阶码全1时(E=2047),如果M也是0,表示正无穷/负无穷
- 当阶码全1时(E=2047),如果M不是0,表示非数值(NaN)
64位的双精度浮点数表示的最大值和最小值:(绝对值)
1.0×2^-1022(M=0,E=1)即2^-1022(2-2^-54)×2^1023(M=0,E=2046)即大约2^1024