🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
這個問題你可能在面試、線上 Bug、甚至隨手寫 Demo 的時候都見過:
console.log(0.1 + 0.2 === 0.3); // false
很多人第一反應是“浮點數精度問題”,但如果繼續追問:
- 為什麼偏偏是
0.1、0.2這種小數出問題? - “精度”到底精在哪一位、丟在哪一步?
- 實際開發裏應該怎麼比較、怎麼計算才穩?
這篇文章按“現象 → 原因 → 解決 → 面試回答”的順序,把它講透。
先看現象
console.log(0.1 + 0.2); // 0.30000000000000004 console.log(0.1 + 0.7); // 0.7999999999999999 console.log(0.3 - 0.2); // 0.09999999999999998 console.log(0.1 * 3); // 0.30000000000000004
console.log(0.5 + 0.5); // 1 console.log(0.25 + 0.25); // 0.5 console.log(0.125 * 8); // 1
為什麼?
根本原因:二進制無法精確表示某些十進制小數
計算機用二進制存儲數字。十進制的 0.5 在二進制裏是 0.1,能精確表示。但十進制的 0.1 在二進制裏是:
0.0001100110011001100110011001100110011... (無限循環)
就像十進制裏 1/3 = 0.333... 無限循環一樣,0.1 在二進制裏也是無限循環的。
但計算機內存有限,不能存無限長的數字,必須在某個位置截斷。JavaScript 用的是 IEEE 754 雙精度浮點數,只有 64 位,其中 52 位用來存小數部分。
截斷就意味着誤差。
哪些數能精確表示?
能被 2 的冪次整除的小數,在二進制裏都能精確表示:
// 這些都是精確的 0.5 = 1/2 = 0.1 (二進制) 0.25 = 1/4 = 0.01 (二進制) 0.125 = 1/8 = 0.001 (二進制) 0.0625 = 1/16 = 0.0001 (二進制)
而 0.1 = 1/10,10 = 2 × 5,有因子 5,所以在二進制裏是無限循環。
規律:分母只包含因子 2 的分數,在二進制裏能精確表示。
怎麼解決?
方案一:容差比較(推薦)
既然有誤差,那就別用 === 比較,改用"誤差小於某個值":
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true
Number.EPSILON 是 JavaScript 裏最小的可表示精度差,約等於 2.22e-16。
方案二:轉成整數算
小數不精確,整數是精確的。把小數轉成整數,算完再轉回來:
// 0.1 + 0.2
const result = (0.1 * 10 + 0.2 * 10) / 10; // 0.3
// 封裝一下
function add(a, b) {
const precision = Math.max(
(a.toString().split('.')[1] || '').length,
(b.toString().split('.')[1] || '').length
);
const factor = Math.pow(10, precision);
return (Math.round(a * factor) + Math.round(b * factor)) / factor;
}
console.log(add(0.1, 0.2)); // 0.3
方案三:toFixed 四捨五入
簡單粗暴,直接四捨五入:
const result = parseFloat((0.1 + 0.2).toFixed(10)); console.log(result); // 0.3
注意 toFixed 返回的是字符串,要用 parseFloat 轉回數字。
方案四:用專門的庫
金融計算這種對精度要求高的場景,用專門的庫:
// decimal.js import Decimal from 'decimal.js'; const a = new Decimal(0.1); const b = new Decimal(0.2); console.log(a.plus(b).toString()); // "0.3"
實際開發中怎麼選?
面試怎麼答?
JavaScript 用 IEEE 754 雙精度浮點數存儲數字。十進制的 0.1 在二進制裏是無限循環小數,但只有 52 位存儲空間,必須截斷,所以有精度損失。0.1 和 0.2 存儲時都有微小誤差,加起來誤差累積,結果就不等於 0.3 了。
解決方案有幾種:比較時用容差比較、計算時轉成整數、或者用 decimal.js 這樣的高精度庫。
如果面試官繼續問"IEEE 754 是什麼",可以補充:
IEEE 754 是浮點數的存儲標準,用 64 位存一個數字:1 位符號位、11 位指數位、52 位尾數位。這個標準是為了讓不同計算機上的浮點運算結果一致。
一道相關的坑題
console.log(0.1 + 0.2 - 0.3); // ?
答案不是 0,而是 5.551115123125783e-17。
再來一道:
console.log(9999999999999999); // ?
答案是 10000000000000000。因為超出了安全整數範圍(2^53 - 1),精度丟失了。