为什么js中的0.1+0.2≠0.3,而0.2+0.2=0.4?
前言
很多人都遇到过这个问题:为什么在计算机中,0.1 加 0.2 的结果不等于 0.3 呢?
var result = 0.1 + 0.2;
console.log(result); // 输出0.30000000000000004
其实,这是由计算机的小数精度问题导致的。
问题原理
在计算机中,小数通常使用二进制表示。但是,有些十进制小数在二进制下是无限循环的,比如 0.1 和 0.2。这就导致了在计算 0.1+0.2 时,计算机无法精确地表示这两个小数,从而产生了误差,结果不等于 0.3。
你可能会问:既然 0.1 和 0.2 都是无限循环的数,为什么只有 0.1 会出现问题呢?
这是因为 0.1 和 0.2 在二进制下的无限循环部分不同,从而导致了计算机处理的误差累积不同。
在计算机中,浮点数采用二进制进行表示,一个浮点数在内存中的二进制形式由三部分组成:符号位、指数位和尾数位。在 JavaScript 中,采用 IEEE 754 标准,使用 64 位存储一个浮点数,其中符号位占 1 位,指数位占 11 位,尾数位占 52 位。
举个例子,0.1 在二进制下是这样表示的:
0.1(十进制) = 0.0001100110011001100110011001100110011001100110011...(二进制)
而 0.2 在二进制下是这样表示的:
0.2(十进制) = 0.0011001100110011001100110011001100110011001100110...(二进制)
我们可以看到,它们的无限循环部分是不同的,将 0.1 转换为二进制时,其二进制循环长度是无限的,计算机在进行精度计算时会丢失精度,产生误差,而将 0.2 转换为二进制时,其循环长度是有限的,因此在计算机中进行精度计算时会得到相对精确的结果。
那么,为什么 0.1 是无限的,而 0.2 是有限的呢?
在二进制中,只有像 1/2、1/4、1/8、1/16 这样的分数能够用有限位的二进制来表示。因此,如果一个分数的分母能够分解为形如 2^m * 5^n
的形式,那么在二进制中就可以被准确地表示。
例如,0.2 可以表示为 1/5,而 5 可以分解为 5 = 5^1 = 2^0 * 5^1
,因此 0.2 可以被准确地表示为二进制小数 0.001100110011...
。在这个二进制表示中,循环节的长度是有限的,因此在计算机中能够精确地表示 0.2。
相反,0.1 可以表示为 1/10,但是 10 不能被分解为 2 和 5 的幂的乘积,因此在二进制下无法精确地表示 0.1。0.1 在二进制下的表示是一个无限循环小数 0.00011001100110011...
,因此在计算机中表示时会存在舍入误差。
因此,在计算 0.1+0.2 时,误差会累积,导致最终结果不等于 0.3。而在计算 0.2+0.2 时,这个误差不会累积,所以最终结果能够精确表示为 0.4。
解决办法
既然知道了问题的原因,那么如何解决这个问题?
可以使用如下几种方案。
1. 使用 toFixed()
方法将结果四舍五入到指定的小数位数。
var result = 0.1 + 0.2;
result = result.toFixed(1); // 将结果四舍五入到一位小数
console.log(result); // 输出0.3
2. 使用 Math.round()
方法将结果四舍五入到最接近的整数。
var result = 0.1 + 0.2;
result = Math.round(result * 10) / 10; // 将结果四舍五入到一位小数
console.log(result); // 输出0.3
3. 将浮点数转换为整数进行计算,然后再将结果除以相应的因子。
var result = (0.1 * 10 + 0.2 * 10) / 10;
console.log(result); // 输出0.3
这里将 0.1 和 0.2 乘以 10,然后将它们相加得到 3,再除以 10,得到 0.3。
需要注意的是,在处理浮点数计算时,要尽量避免使用等于(==
)来比较浮点数,因为由于精度问题,可能会出现意外的结果。可以使用 Math.abs()
方法来比较两个浮点数之间的差异是否小于某个阈值,例如:
var a = 0.1 + 0.2;
var b = 0.3;
if (Math.abs(a - b) < 0.000001) {
console.log('a等于b');
} else {
console.log('a不等于b');
}
这里使用了一个很小的阈值(0.000001
),用于比较 a
和 b
之间的差异是否小于该阈值。
你还可以使用 Number.EPSILON
来表示一个很小的阈值,它是 JavaScript 中能够表示的最小精度,即 2^-52
。因此,如果两个浮点数之间的差异小于 Number.EPSILON
,那么我们就可以认为它们相等。
var a = 0.1 + 0.2;
var b = 0.3;
if (Math.abs(a - b) < Number.EPSILON) {
console.log('a等于b');
} else {
console.log('a不等于b');
}
当然,你还可以借助第三方库来解决这个问题,比如 decimal.js、big.js 等,本文不再展开。
总结
在计算机中,所有的数字都是以二进制的形式存储的,因此,所有的小数都是以二进制小数的形式存储的。但是,二进制小数的表示并不是唯一的,因此,计算机中的小数精度也是有限的。
所以,如果你遇到了计算机中小数精度的问题,不要慌张,这是很常见的情况。只需要理解它的本质,就可以采取相应的解决措施了。
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=3167