一个经典现象

在大多数编程语言中,0.1 + 0.2 的结果是 0.30000000000000004,而不是 0.3。

这不是 bug,而是二进制浮点数的固有特性。

根本原因

计算机用二进制表示浮点数。问题在于:很多在十进制中看起来很简洁的小数,在二进制中是无限循环的。

十进制的 0.1,换算成二进制是 0.00011001100110011...——"0011"无限循环。

这和十进制无法精确表示 1/3(0.3333...)是同一类问题,只是发生在不同的进制之间。

因为存储位数有限(IEEE 754 双精度为 64 位,其中尾数 52 位),无限循环的部分必须被截断,于是产生误差。

IEEE 754 的结构

标准的浮点数由三部分组成:

[符号位][指数部分][尾数部分]

以 64 位双精度为例:1 位符号,11 位指数,52 位尾数(另有隐含的最高位 1)。

这种表示法本质上是用科学计数法:数值 = 符号 × 尾数 × 2^指数。

它带来的一个重要后果是:浮点数的分布不均匀。靠近 0 的地方数值密集、精度高;远离 0 的地方数值稀疏、精度低。这与整数的均匀分布完全不同。

哪些数能精确表示

只有能写成"有限位二进制小数"的数才能被精确表示。这类数都可以表示为 m × 2^n 的形式(m、n 为整数)。

  • 0.5(1/2)✓
  • 0.25(1/4)✓
  • 0.125(1/8)✓
  • 0.1(1/10)✗

实际影响与应对

不要用浮点数做金额计算。金融场景应使用定点数(如以"分"为单位的整数)或十进制库(如 Python 的 Decimal)。

比较浮点数不要用 ==。应该判断两个数的差的绝对值是否小于一个很小的阈值(epsilon)。

误差会累积。大量浮点运算的误差可能逐步放大,在科学计算和迭代算法中需要专门分析数值稳定性。

一个有趣的例外

并非所有语言都是这样。一些语言(如 SQL 的 DECIMAL 类型、Python 的 Decimal、Java 的 BigDecimal)专门提供了十进制精确算术,代价是性能较低、内存占用更大。

选择哪种,取决于你的场景是要快还是要准。

说明:本文为浮点数表示的公开知识整理。