在很多编程语言中,我们都会发现一个奇怪的现象,就是计算 0.1 + 0.2,它得到的结果并不是 0.3,比如 C、C++、JavaScript 、Python、Java、Ruby 等,都会有这个问题。

为此,有人还做出了一个 https://0.30000000000000004.com/ 网站,在这个网站上我们可以找到哪些编程语言在计算 0.1 + 0.2 的结果并不是 0.3,而是 0.30000000000000004。

我们可以用 JavaScript 来做演示,计算 0.1 + 0.2,它得到的结果并不是0.3,而是 0.30000000000000004。

当然如果你把它作为条件判断语句,返回的也是 false:

还有其他的例子:

6 * 0.1 = 0.6 但计算机显示为 0.6000000000000001​
0.11 + 0.12 = 0.23 但计算机显示为 0.22999999999999998​
0.1 + 0.7 = 0.8 但计算机显示为 0.7999999999999999​
0.3+0.6 = 0.9 但计算机显示为0.8999999999999999

而以下几个计算式却能得到我们想要的结果:

这是为什么呢?

简单来说,计算机使用基于二进制的浮点数,而我们人类使用基于十进制的浮点数。

在二进制中,,浮点数通常使用 IEEE 754 标准进行表示,无法准确表示的小数有 0.1、0.2 或 0.3 这样的数字,因为它使用的是二进制浮点格式。

IEEE 754 标准可以参见:https://zh.wikipedia.org/wiki/IEEE_754

背后原理

在十进制系统中,如果一个分数使用基数(10)的质因数来表示,那么它可以被精确地表示。

10 的质因数是 2 和 5。

因此,1/2、1/4、1/5 (0.2)、1/8 和 1/10 (0.1) 可以被精确地表示,因为分母使用了 10 的质因数。

而 1/3、1/6 和 1/7 是无限循环的小数,因为分母使用了 3 或 7 的质因数。

在二进制(计算机使用的系统)中,如果一个分数使用基数(2)的质因数来表示,那么它可以被精确地表示。

2 是 2 的唯一质因数。

因此,1/2、1/4 和 1/8 都可以被精确地表示,因为分母使用了 2 的质因数。

而 1/5 (0.2) 或 1/10 (0.1) 是无限循环的小数,因为分母使用了 5 或 10 的质因数。

所以当我们尝试表示像 0.1 这样的十进制小数时,计算机会使用一个近似值。这个近似值是通过将无限循环的二进制小数转换为有限位数的浮点数表示来实现的。

因此,当我们在计算机中进行浮点数运算时,结果可能会有微小的误差。

例如,0.1 在二进制中的近似表示可能是 0.000110011001100...,但在计算机的浮点数表示中,它可能被截断或舍入为 0.00011001100110,这就导致了 0.1 + 0.2 在计算机中可能不等于 0.3,而是略微有所偏差。

0.1 是十分之一(1/10),要得到 0.1 的二进制表示(即二进制形式),我们需要使用二进制长除法,即将二进制数1除以二进制的 1010(即1/1010),如下所示:

因此,0.1 在二进制中的表示为 0.0001100110011001100110011...(无限循环)。

这个无限循环的模式 0011 会一直重复下去,因为二进制系统只能通过这种方式来近似表示十进制中的 0.1。

在实际的计算机系统中,这个无限循环的小数会被截断为有限位数,以便存储和计算。这就导致了在计算机中进行二进制浮点数运算时,可能会出现精度损失,从而使得 0.1 和 0.2 的和不完全等于0.3。

十进制小数转二进制

还有一种更容易理解的方法(采用 *2 取整法),例如我们要把十进制数的小数 0.875 转换为二进制数,只需将十进制数的小数部分乘以 2,然后提取整数部分,直到小数部分变为 0。

将上面提取的整数部分排列的结果 111 变成以二进制表示的 .875

二进制数 1101.111 整数部分为 1101 ,小数部分为 111,就是十进制数 13.875 转换为二进制的结果。

按以上的方法我们将十进制小数 0.1 转化为二进制就是:

0.1 = 0.0001100110011001100110011001100110011001100110011001101...

十进制小数 0.2 转化为二进制就是:

0.2 = 0.001100110011001100110011001100110011001100110011001101...


解决办法

1、小数先转整数

可以先把小数转换成整数,再相加之后转回小数,如下实例:

(0.1*10 + 0.2*10)/10

2、使用 toFixed() 方法

toFixed() 方法可以将一个数字转换为指定小数位数的字符串表示形式。

下面是一个使用 toFixed() 方法解决浮点数精度问题的例子:

let sum = 0.1 + 0.2;
console.log(sum.toFixed(2)); // 输出: 0.30

需要注意的是,toFixed()方法虽然在显示上解决了问题,但它并没有改变数字的实际值,它只是改变了数字的表示形式。

如果你需要进行精确的数学运算,可能需要使用其他方法,比如引入一个精度更高的数值类型或者使用第三方的数学库来处理浮点数运算。

3、使用 decimal.js 库

在 JavaScript 中处理浮点数的精度问题时,使用 decimal.js 库是一个更为精确和可靠的解决方案。

decimal.js 是一个任意精度的十进制数学库,它能够避免原生 JavaScript 中浮点数运算的不精确性。

Github 地址:https://github.com/MikeMcl/decimal.js

首先,你需要在你的项目中引入 decimal.js 库。

使用 npm 安装:

npm install decimal.js

在 HTML 中引入:

<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.0/decimal.min.js"></script>

接下来,你可以使用 decimal.js 来处理浮点数的运算,下面是一个例子:

实例

// 引入Decimal构造函数
const Decimal = decimal. Decimal;

// 创建两个Decimal对象
let a = new Decimal('0.1');
let b = new Decimal('0.2');

// 进行加法运算
let sum = a.plus(b);

// 输出结果
console.log(sum.toString()); // 输出: 0.3

使用 decimal.js 库得到的结果是准确的 0.3,而不是原生 JavaScript 中的近似值。