IEEE 754 浮点数标准:深入理解计算机算术原理
如果您从事过编程工作,很可能遇到过一个奇怪的现象:0.1 + 0.2 并不等于 0.3。相反,您会得到类似 0.30000000000000004 的结果。这并不是您所用编程语言的 bug,而是计算机使用 IEEE 754 标准表示实数的必然结果。
在本指南中,我们将揭开 IEEE 754 标准的神秘面纱,解释浮点数是如何存储的,并为在代码中处理精度问题提供实用建议。
什么是 IEEE 754?
IEEE 浮点算术标准 (IEEE 754) 是应用最广泛的浮点计算标准。它于 1985 年确立,定义了实数在二进制中的表示格式及其运算规则。
最常见的格式包括:
- 单精度 (32 位):在 C/C++/Java 中对应
float。 - 双精度 (64 位):在 C/C++/Java 中对应
double,也是 JavaScript 和 Python 中的默认数字类型。
核心原理:浮点数的结构
浮点数的表示方式类似于科学计数法 ($1.23 \times 10^4$),只不过是二进制形式。它由三部分组成:
- 符号位 (1 位):
0表示正数,1表示负数。 - 指数 (Exponent):决定数字的缩放范围。
- 尾数 (Mantissa/Significand):表示有效数字。
64 位双精度布局:
- 符号位:1 位
- 指数:11 位
- 尾数:52 位
计算公式为: $(-1)^{sign} \times (1.mantissa) \times 2^{exponent - bias}$
为什么 $0.1 + 0.2 \neq 0.3$?
根本原因是大多数十进制小数无法在二进制中精确表示。
- 在十进制中,如果一个分母的质因数只有 2 和 5(10 的因数),则该分数可以精确表示。
- 在二进制中,只有当分母的质因数只有 2 时,分数才能精确表示。
$0.1$ 等于 $1/10$。由于 10 包含因数 5,它在二进制中变成了一个无限循环序列:
0.00011001100110011...
计算机必须截断这个无限序列以适应 32 位或 64 位的存储空间,从而导致了我们看到的微小误差。
特殊值
IEEE 754 还定义了几个特殊值来处理边界情况:
- NaN (Not a Number):未定义运算的结果(如
0/0)。 - Infinity ($\infty$):溢出或除以零的结果(如
1/0)。 - 负零 (-0):在某些计算中与正零有所区别。
开发者最佳实践
- 永远不要用
==比较浮点数:始终检查差值是否小于一个极小值 (epsilon)。if (Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON) { ... } - 涉及货币时使用定点数:对于财务计算,请使用专门的库(如
decimal.js)或将数值存储为整数(例如以“分”为单位而不是“元”)。 - 注意数值范围:双精度可以表示非常大的数字,但随着数值增大,精度会降低。
常见问题 FAQ
问:浮点数运算是非确定性的吗? 答:通常不是。给定相同的输入和相同的舍入模式,IEEE 754 应该产生相同的结果。但是,不同的编译器或 CPU 指令(如 FMA)可能会导致细微差异。
问:什么是 "BigInt"? 答:JavaScript 中的 BigInt 用于处理任意精度的整数。它不能处理小数。对于小数,您需要 Decimal 库。
问:双精度有多少位十进制精度? 答:一个 64 位双精度浮点数大约有 15 到 17 位有效十进制数字。