(转)为什么JS中0.1+0.2不等于0.3?

作者 Zhendong Ho 日期 2019-02-11
(转)为什么JS中0.1+0.2不等于0.3?

JS中数字运算时,有一个叫做数字运算中的精度缺失的问题。这篇文章,就带着大家了解下JS运算中精度的缺失问题。

首先我们先来看一个例子:

console.log(0.1 + 0.2);		//结果是0.30000000000000004,而不是0.3

这里0.1 + 0.2 != 0.3 这个就是我们要解决的问题了

精度缺失原因

要弄清这个问题的原因,首先我们需要了解下在计算机中数字是如何存储和运算的。在计算机中,数字无论是定点数还是浮点数都是以多位二进制的方式进行存储的。

在JS中数字采用的IEEE 754的双精度标准进行存储,我们可以无需知道他的存储形式,只需要简单的理解成就是存储一个数值所使用的二进制位数比较多而已,这样得到的数会更加精确。

这里为了简单直观,我们使用定点数来说明问题。在定点数中,如果我们以8位二进制来存储数字。

对于整数来说,十进制的35会被存储为: 00100011 其代表 2^5 + 2^1 + 2^0。

对于纯小数来说,十进制的0.375会被存储为: 0.011 其代表 1/2^2 + 1/2^3 = 1/4 + 1/8 = 0.375。

而对于像0.1这样的数值用二进制表示你就会发现无法整除,最后算下来会是 0.000110011….由于存储空间有限,最后计算机会舍弃后面的数值,所以我们最后就只能得到一个近似值

在JS中采用的IEEE 754的双精度标准也是一样的道理,我们且不管这个标准下的存储方式跟定点数存储有何不同,单单在这一点上他们都是相同的,也就是存储空间有限,当出现这种无法整除的小数的时候就会取一个近似值,在js中如果这个近似值足够近似,那么js就会认为他就是那个值

比较拗口,举个例子:

console.log(0.1000000000000001);
//0.1000000000000001(中间14个0,会打印出它本身)

console.log(0.10000000000000001);
//0.1(中间15个0,js会认为这两个值足够接近,所以会显示0.1)

所以我们现在应该可以理解,就是说由于0.1转换成二进制时是无限循环的,所以在计算机中0.1只能存储成一个近似值。另外说一句,除了那些能表示成 x/2^n 的数可以被精确表示以外,其余小数都是以近似值得方式存在的

在0.1 + 0.2这个式子中,0.1和0.2都是近似表示的,在他们相加的时候,两个近似值进行了计算,导致最后得到的值是0.30000000000000004,此时对于JS来说,其不够近似于0.3,于是就出现了0.1 + 0.2 != 0.3 这个现象。当然,也并非所有的近似值相加都得不到正确的结果

有时两个近似值进行计算的时候,得到的值是在JS的近似范围内的,于是就可以得到正确答案。至于哪些值计算后能得到正确结果,哪些不能,我们也不需要去记。

最好的方法就是我们想办法规避掉这类小数计算时的精度问题就好了,那么最常用的方法就是将浮点数转化成整数计算。因为整数都是可以精确表示的。

解决方法

那么应该怎样来解决0.1 + 0.2等于0.3呢?

最好的方法是设置一个误差范围值,通常称为“机器精度”。而对于 JavaScript 来说,这个值通常是2^-52。而在 ES6 中,已经为我们提供了这样一个属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0。这个时候我们只要判断(0.1 + 0.2) - 0.3小于Number.EPSILON,在这个误差的范围内就可以判定0.1 + 0.2 === 0.3为true了。

function numbersEqual(a,b) {
return Math.abs(a-b) < Number.EPSILON;
}
var a = 0.1 + 0.2, b = 0.3;
console.log(numbersEqual(a,b));    //true