为什么0.1 + 0.2 不等于 0.3 ?

本文涉及的产品
大数据开发治理平台 DataWorks,不限时长
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,5000CU*H 3个月
简介: 这篇编程技术文章探讨了为什么在多种编程语言中,0.1 + 0.2 不等于 0.3 的现象。问题源于计算机使用二进制浮点数表示小数,而二进制无法精确表示某些十进制分数,如 0.1 和 0.2。这导致它们在计算机内部被近似表示,从而在相加时产生微小误差。文章通过示例和图片解释了二进制浮点数的表示原理,并提供了将小数转换为整数再相加以及使用 `toFixed()` 方法或 decimal.js 库等解决精度问题的方法。


在很多编程语言中,我们都会发现一个奇怪的现象,就是计算 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 中的近似值。

相关文章
|
1月前
|
算法 前端开发
最大公因数等于 K 的子数组数目
最大公因数等于 K 的子数组数目
30 0
|
1月前
|
Python
一个大于1的自然数,除了1和它本身外,不能被
一个大于1的自然数,除了1和它本身外,不能被
|
9月前
输入一个整数,判断大于0小于0还是等于0
输入一个整数,判断大于0小于0还是等于0
|
9月前
让用户输入x的值,如果x的值如果小于1,y=x,x如果大于等于1并且小于10,y=2x。。。 // x x < 1 // y={ 2X 1<=x
让用户输入x的值,如果x的值如果小于1,y=x,x如果大于等于1并且小于10,y=2x。。。 // x x < 1 // y={ 2X 1<=x
|
9月前
|
Go
怎样判断一个数是否为偶数
怎样判断一个数是否为偶数
54 0
|
1月前
|
Python
计算小于或等于n的非负整数区间包含的1的数量
计算小于或等于n的非负整数区间包含的1的数量
27 0
|
1月前
位与运算&小于相等判断==的优先级
位与运算&小于相等判断==的优先级
|
7月前
|
C++
37 C++ - 等于和不等于(==、!=)运算符重载
37 C++ - 等于和不等于(==、!=)运算符重载
38 0
小于等于K的最大子数组累加和
小于等于K的最大子数组累加和
|
机器学习/深度学习
欧拉函数:求小于等于n且与n互质的数的个数
求小于等于n且与n互质的数的个数 互质穷举法 互质:两个数互质代表两者最大公约数为1 最大公约数求法:辗转相除法,最小公倍数:较大值除以最大公约数乘以较小值 辗转相除法: 较大的数a取模较小的数b,得取模值c 若取模值等于0 则最大公约数为取模值,否则继续下一步 a与c再次取模,回到第二步 //求最大公约数gcd以及最大公倍数lcm // 36 24 36/24 // 24 12 24/12 // 0 结束最大公约数为12 // 求最小公倍数 // lcm(a, b) = (a * b)/g
94 0

热门文章

最新文章