原因
js中的数字
JavaScript的Number
类型为双精度IEEE 754 64位浮点类型。
那么一个Number的二进制的形态为
- 第0位:符号位,0表示正数,1表示负数(s)
- 第1位到第11位:储存指数部分(e)
- 第12位到第63位:储存小数部分(即有效数字)f
JS中Number的最大安全整数为(2^52 -1) = 4503599627370495 是一个16位的整数,因此当我们赋值大于安全整数的一个Number给一个变量的时候,那么到输出的时候就会得到的值和我们赋予的值不同,
二进制的转换
由于JavaScript的Number
类型为双精度IEEE 754 64位浮点类型,因此当JS要表示一个数字的时候需要进行二进制的转换
整数转二进制
除2 (取余)
例如 21 得 10101 如果计算机是8位 16位 32位 64位 不够的就往前补0
21 /2 ----------余 1
10/2 ----------余 0
5/2 ----------余 1
2/2 ----------余 0
1/2 ----------余 1
负整数转二进制
先是将对应的正整数转换成二进制后,对二进制取反(0-1对调),然后对结果再加一
例子 -21 得 01011
21 的二进制表示为: 10101
取反: 01010
加一 : 01011
二进制中 0+1 = 1 ; 1+0 = 1; 1+1 = 10 就相当于进一位
十进制(小数)转二进制
乘2(取整)
例如 0.125 得0.001
0.125×2=0.25 ---> 0
0.25×2=0.5 ---> 0
0.5×2=1.0 ---> 1
例如:0.1 得 0.000110.... 答案是一个无线的小数
0.1 * 2 = 0.2 ---> 0
0.2 * 2 = 0.4 ---> 0
0.4 * 2 = 0.8 ---> 0
0.8 * 2 = 1.6 ---> 1
0.6 * 2 = 1.2 ---> 1
0.2 * 2 = 0.4 ---> 0
.....
因此
0.1 -> 0.0001100110011001...(无限循环)
0.2 -> 0.0011001100110011...(无限循环)
但是由于IEEE 754尾数位数限制,需要将后面多余的位截掉,截取后精度发生了一些变化,然后在(二进制的相加对阶运算)2个都有精度变化的0.1+0.2就很不幸运的成为了0.30000000000000004....
但是由于我们前面有提到尾数f的固定长度是52位,是4503599627370495 是一个16位的整数,所以JS会有一个类似 toPrecision(16) 来做精度运算,0.30000000000000004....就被截取为了0.30000000000000004,所以0.1+0.2!===0.3,
总结原因就是:精度损失可能出现在进制转化和对阶运算过程中
解决方法
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);