人人都会AI编程

32.6 浮点数精度问题与解决方案

更新时间:2026-07-11

如果你在浏览器控制台输入 0.1 + 0.2,得到的答案不是 0.3,而是 0.30000000000000004。这不是 JavaScript 的 bug,而是所有使用 IEEE 754 双精度浮点数标准的语言(如 Python、Java、C 等)都存在的固有问题。理解它并不需要高深的数学,只需要知道两件事:为什么会出现,以及怎么处理。

为什么 0.1 + 0.2 ≠ 0.3

计算机内部使用二进制存储数字。十进制中能精确表示的小数(如 0.5 = 1/2),在二进制中同样能精确表示。但 0.1 和 0.2 这类十进制小数,在二进制中是无限循环小数:

  • 0.1(十进制) = 0.0001100110011001100110011...(二进制,1100 循环)
  • 0.2(十进制) = 0.001100110011001100110011...(二进制,1100 循环)

JavaScript 的 Number 类型采用 64 位双精度浮点数(1 位符号 + 11 位指数 + 52 位尾数),存储空间有限,只能截断到 52 位。这个截断引入了一个极微小的“舍入误差”。当多个带有误差的数进行运算时,误差可能被放大,最终体现在十进制的显示结果上。

什么场景会踩坑

  • 相等比较0.1 + 0.2 === 0.3 返回 false
  • 金额计算:如果把商品单价用浮点数存储并累加,最后的总额可能对不上账。
  • 循环累加for (let i = 0; i < 10; i += 0.1) 可能因为误差导致循环次数与预期不符。
  • toFixed 舍入(1.335).toFixed(2) 在某些引擎中返回 "1.33" 而非 "1.34",因为内部表示可能略小于 1.335。

实用解决方案

1. 容忍一个极小误差来判断相等

不直接比较 a === b,而是看它们差的绝对值是否小于一个极小值(通常取 Number.EPSILON,即 2 的 -52 次方):

function isEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}

isEqual(0.1 + 0.2, 0.3); // true

适用于比较运算结果,但不能从根本上解决精度问题。

2. 使用 toFixed 或 toPrecision 格式化显示

当只需要向用户展示时,用 toFixed(n) 指定保留小数位数。注意 toFixed 返回的是字符串,且可能存在舍入问题,更适合最终展示,不适合中间运算。

const result = 0.1 + 0.2;
console.log(result.toFixed(1)); // "0.3"

3. 将小数转为整数进行运算(核心:金额场景的最可靠方案)

把小数乘以 10 的 n 次方变成整数,计算完再除回来。因为整数在 53 位范围内是精确的:

// 计算 0.1 + 0.2
const result = (0.1 * 10 + 0.2 * 10) / 10; // 0.3

实际开发中,可以封装一个通用的数学运算库,例如:

function add(num1, num2) {
  const baseNum1 = (num1.toString().split('.')[1] || '').length;
  const baseNum2 = (num2.toString().split('.')[1] || '').length;
  const base = Math.pow(10, Math.max(baseNum1, baseNum2));
  return (num1 * base + num2 * base) / base;
}

不过,这种手写方案在多个连续运算时比较繁琐,且需注意乘除本身也可能引入浮点误差。

4. 使用第三方库(推荐用于生产环境的金额/精密计算)

如果业务涉及金融、订单、计价等场景,直接用成熟的高精度计算库:

  • decimal.js:提供任意精度的十进制运算,API 贴近原生 Number。
  • big.js:体积更小,适合基础运算。
  • bignumber.js:功能全面,支持多种进制和格式化。
import Decimal from 'decimal.js';

const result = new Decimal(0.1).plus(0.2);
console.log(result.toString()); // "0.3"

5. 在明确整数范围内,使用 BigInt

对于超出 Number 安全范围的大整数(如 16 位以上的 ID),可以使用 ES2020 引入的 BigInt

const big = 9007199254740991n + 1n; // 精确的整数运算

BigInt 不支持小数,因此不能直接解决浮点精度问题。

选择建议

  • 单纯展示:用 toFixedIntl.NumberFormat 格式化。
  • 比较运算结果:用 Math.abs(a - b) < Number.EPSILON
  • 多次累加、加减金额:用整数法(乘以 100 后再运算,最后除以 100)或直接引入第三方库。
  • 生产级的金融或科学计算:毫不犹豫使用 decimal.js 等专业库,不要自己造轮子。

理解浮点精度问题不是为了追求“绝对精确”(没有系统能做到无限精度),而是在合适的场景下选择正确的方法,让误差不在业务逻辑中累积成事故。