为什么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),用于比较 ab 之间的差异是否小于该阈值。

你还可以使用 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.jsbig.js 等,本文不再展开。

总结

在计算机中,所有的数字都是以二进制的形式存储的,因此,所有的小数都是以二进制小数的形式存储的。但是,二进制小数的表示并不是唯一的,因此,计算机中的小数精度也是有限的。

所以,如果你遇到了计算机中小数精度的问题,不要慌张,这是很常见的情况。只需要理解它的本质,就可以采取相应的解决措施了。

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注