JavaScript 浮点数精度问题的正确应对策略

本文解析为何对 0.2 和 0.1 执行 ×10 再 ÷10 并不能真正“解决”浮点误差,而只是偶然掩盖了问题;强调可靠方案应是显式舍入(如 math.round),并说明其原理与适用边界。

JavaScript 中 0.2 + 0.1 !== 0.3 是经典浮点精度问题:二进制无法精确表示十进制小数(如 0.1 的二进制是无限循环小数 0.0001100110011…₂),导致计算结果出现微小偏差(0.30000000000000004)。

表面上看,let y = (0.2 * 10 + 0.1 * 10) / 10 得到 0.3,似乎“修复”了问题——但这并非普适解法,而是巧合。原因在于:

  • 0.2 * 10 实际为 2.0000000000000004,0.1 * 10 为 1.0000000000000002;
  • 它们的和约为 3.0000000000000004,除以 10 后为 0.30000000000000004;
  • JavaScript 在字符串化或某些显示场景中会自动舍入到有效小数位(如 console.log(y) 或 DOM 渲染时隐式调用 toString()),使 0.30000000000000004 显示为 "0.3" —— 这不是数值修正,而是显示截断。

⚠️ 更危险的是:该方法在其他数值上极易失效。例如:

let a = 0.29 * 10; // 2.8999999999999995
let b = 0.01 * 10; // 0.10000000000000002
console.log((a + b) / 10); // 0.30000000000000004 —— 仍未修正

真正可靠的方案是显式控制精度:先放大、再舍入、最后还原:

// 正确做法:对中间结果使用 Math.round 确保整数精度
let y = (Math.round(0.2 * 10) + Math.round(0.1 * 10)) / 10;
// → (2 + 1) / 10 = 0.3 (数值层面严格等于 0.3)

// 通用函数(支持任意小数位)
function addFloat(a, b, decimal = 1) {
  const factor = 10 ** decimal;
  return (Math.round(a * factor) + Math.round(b * factor)) / factor;
}
console.log(addFloat(0.2, 0.1));        // 0.3
console.log(addFloat(0.29, 0.01, 2));  // 0.30

? 总结:

  • ✖️ 单纯乘除 10 不解决精度问题,仅依赖隐式显示舍入,不可靠;
  • ✖️ 盲目放大至 10**10 反而可能引入更大舍入误差(超出安全整数范围 Number.MAX_SAFE_INTEGER);
  • ✔️ 使用 Math.round() 显式取整是可控、可预测、可复用的实践方案;
  • ? 对金融等高精度场景,建议使用 BigInt 或专用库(如 decimal.js),避免浮点运算本身。