0.1+0.2 为什么不等于 0.3?计算机真的算错了吗?

TL;DR

许多十进制小数无法用有限位二进制精确表示,计算机只能保存最接近的值。微小误差在显示时常被隐藏,却会在比较、累计和金额计算中冒出来。本文用三分之一的类比讲清浮点数,并给出可靠的处理原则。

许多十进制小数无法用有限位二进制精确表示,计算机只能保存最接近的值。微小误差在显示时常被隐藏,却会在比较、累计和金额计算中冒出来。本文用三分之一的类比讲清浮点数,并给出可靠的处理原则。

0.1+0.2 为什么不等于 0.3?计算机真的算错了吗?

在许多编程语言里输入 0.1 + 0.2 == 0.3,结果可能是 false。把和打印得更精细,还会看到类似 0.30000000000000004 的数字。计算机连小学加法都算错了吗?

它没有算错,而是在忠实计算自己实际保存的两个近似值。问题从输入小数的那一刻就开始了。

IEEE 754 单精度浮点数的符号位、指数位和尾数位结构

十进制有限,二进制未必有限

在十进制中,三分之一写成 0.3333…,无论给多少位都无法精确结束。我们只能保留若干位,得到近似值。二进制也有自己的循环小数:十进制的 0.1 转成二进制后是无限重复的 0.000110011…

计算机存储位数有限,只能截取最接近的可表示数。Python 官方的浮点数说明指出,常见 IEEE 754 双精度浮点数用有限精度保存近似分数;0.1 实际对应一个非常接近、却不完全等于十分之一的二进制分数。

0.2 和 0.3 也分别被近似。两个近似数相加后,与“最接近 0.3 的那个近似数”可能落在不同位置,于是直接比较得到不相等。

为什么平时打印仍显示 0.1

如果每次都显示完整近似值,屏幕会充满无用长尾。编程语言通常选择能够往返还原同一浮点数的简短十进制表示,所以显示 0.1。这是一种友好的舍入,不表示内存里真的保存了精确十分之一。

误差通常极小,科学计算、图形渲染和机器学习因此可以高效工作。浮点数的价值正是用固定空间覆盖极宽的数值范围,而不是保证所有十进制小数都精确。

真正危险的是把近似数当身份编号

如果程序用 结果 == 目标值 判断循环是否结束,细小误差可能让条件永远不成立。更稳妥的方法是比较差值是否小于可接受容差,例如判断两个数是否“足够接近”。Python 提供 math.isclose,其他语言也有相似做法。

金额计算则通常需要十进制精度。一个办法是使用整数最小单位,例如把 19.99 元保存为 1999 分;另一个办法是使用十进制定点或高精度 Decimal 类型,并明确舍入规则。不能只在界面上保留两位小数,因为显示舍入没有改变底层累计误差。

地理坐标、传感器数据和物理模拟还要根据量级选择容差。一个固定的 0.000001 对毫米测量可能太大,对天文距离又可能毫无意义。可靠比较应考虑绝对误差与相对误差。

并非所有小数都会出问题

能写成分母为二的幂的分数,可以被有限二进制精确表示。例如 0.5 是二分之一,0.25 是四分之一,0.125 是八分之一。它们就像十进制能精确表示二分之一和五分之一,却不能有限表示三分之一。

整数在自身可表示范围内通常也能精确保存,但范围不是无限的。数值极大后,相邻可表示浮点数的间隔会变宽,甚至出现给大数加一却没有变化的情况。

0.1+0.2 的谜题真正提醒我们的,不是怀疑每一次计算,而是区分“数学实数”和“机器表示”。计算机只能用有限比特描绘无限多数字;理解它怎样近似,才能在需要精确的地方选对工具,在允许误差的地方设对边界。

KEEP READING