Skip to content

0.1 + 0.2 为什么不等于 0.3

这是 JavaScript 及所有用 IEEE 754 浮点数标准的编程语言的常见问题。核心原因:0.1 和 0.2 无法被二进制浮点数精确表示,存储的是近似值,相加后结果与 0.3 的近似值不等

一、IEEE 754 双精度浮点数结构(64 位)

JavaScript 数字均为 64 位双精度浮点数,结构:

符号位(1bit)指数部分(11bits)尾数部分(52bits)
SEM

数值计算:

  • 若 E≠0 且 E≠2047:value = (-1)^S × (1.M)_2 × 2^(E-1023)
  • 若 E=0:value = (-1)^S × (0.M)_2 × 2^(-1022)

二、0.1 的二进制表示(不精确)

十进制 0.1 转二进制是无限循环小数: 0.1₁₀ = 0.00011001100110011...₂(循环节 0011)

因尾数仅 52 位,需截断/舍入。实际存储的 0.1 用代码查看:

js
function toIEEE754Bits(num) {
  const buffer = new ArrayBuffer(8);
  new Float64Array(buffer)[0] = num;
  const view = new Uint8Array(buffer);
  return Array.from(view).reverse().map(b => b.toString(16).padStart(2, '0')).join('');
}
console.log(toIEEE754Bits(0.1)); // "3fb999999999999a"

解析:

  • 符号位 S=0
  • 指数 E=0x3FB=1019 → 实际指数=1019-1023=-4
  • 尾数 M=0x999999999999A
  • 存储值≈(1+0.6)×2^(-4)=0.1(近似值,略大于 0.1)

0.2 同理,存储值略大于 0.2。

三、0.1 + 0.2 的计算过程

CPU 运算步骤:

  1. 对齐两数指数
  2. 尾数右移对齐小数点
  3. 尾数相加
  4. 规格化结果(可能舍入)

实际结果:

js
0.1 + 0.2; // 0.30000000000000004

其 IEEE 表示为"3fd3333333333334",而 0.3 的表示为"3fd3333333333333",最低位不同,故不等。

四、无法精确表示的原因

1/10 的分母含因子 5,二进制中仅能精确表示分母为 2 的幂的分数(如 1/2、3/8),故 0.1 无法精确存储。

五、如何正确比较

用容差判断:

js
function equal(a, b, epsilon = 1e-15) {
  return Math.abs(a - b) < epsilon;
}
console.log(equal(0.1 + 0.2, 0.3)); // true

或用整数运算(如金额以“分”为单位)。

总结

0.1 + 0.2 !== 0.3 是因 IEEE 754 无法精确表示 0.1 和 0.2,相加后误差累积导致。这是浮点数的固有特性,非 JavaScript 漏洞。

尘埃虽微,积之成集;问题虽小,记之为鉴。 雾中低语,心之所向;思绪飘渺,皆可成章。