我们都知道0.1 + 0.2 = 0.3 ,但是在JavaScript中0.1 加0.2 得到的不是0.3 ,而是 0.30000000000000004 。由于这种微小的舍入错误,导致很难测试特定的浮点值。比如下面的例子:
let a = 0.1
let b = 0.2
if (a + b == 0.3) {
console.log("You got 0.3.")
}
这里检测两个数值之和是否等于0.3 。如果两个数值分别是0.05 和0.25 ,或者0.15 和0.15 ,那没问题。但如果是0.1 和0.2 ,如前所述,测试将失败。因此永远不要测试某个特定的浮点值。
let a = 0.05
let b = 0.25
if (a + b == 0.3) {
console.log("You got 0.3.")
}
之所以存在这种舍入错误,是因为使用了 IEEE 754 数值,这种错误并非ECMAScript所独有。其他使用相同格式的语言也有这个问题。 在这之前,我们先来了解一个小概念,为了实现计算,数字是如何表示的,极小数和极大数通常使用科学计数法来表示,例如,0.0006606 用科学计数法的标准化写法为:6.606 x 10^-4 。
- Sign:用来表示数字的正负,
0 为正数,1 为负数 - Exponent:这里用来存储科学计数法的指数,也就是
n 次方 - Mantissa:这里用来存储科学计数法前面的数值
这里用0.125 作为示范,转换为二进制,用的是乘二取整,只计算小数位,从上至下得到0.001 。
0.125 x 2 = 0.25 ,整数部分是0 0.25 x 2 = 0.5 ,整数部分是0 0.5 x 2 = 1.0 ,整数部分是1
接下来,我们把0.1 转换为二进制。
0.1 x 2 = 0.2 ,整数部分是0 0.2 x 2 = 0.4 ,整数部分是0 0.4 x 2 = 0.8 ,整数部分是0 0.8 x 2 = 1.6 ,整数部分是1 0.6 x 2 = 1.2 ,整数部分是1 0.2 × 2 = 0.4 ,整数部分是0
这里我们发现无法进行整除,循环0110 ,那么十进制的0.2 也是会有同样的结果,因为我们可以从上面的结果里面看到从0.2 开始进行乘2 就一样会得到一个无限循环,循环的部分依旧是0110 。那么肯定会出现后续的位置无法存储进去,这样就迫使计算机取一个近似的数字。
我们知道3 个1/3 相加可以得到3/3 也就是1 ,不过这是十进制,就类似于我们把1 除以3 得到的0.333 的无限循环以后再进行相加,很明显这些无限循环的0.333 相加怎么也没有办法得到一个完整的1 。
解决方法: 因为小数乘二取整会有无限循环的情况,但是整数除二取余是不会的,所以整数部分不会出现精度丢失问题。
let a = 0.1
let b = 0.2
if ((a * 10 + b * 10) / 10 == 0.3) {
console.log("You got 0.3.")
}
|