博客 / 詳情

返回

0.1加0.2為什麼不等於0.3

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

這個問題你可能在面試、線上 Bug、甚至隨手寫 Demo 的時候都見過:

console.log(0.1 + 0.2 === 0.3); // false

很多人第一反應是“浮點數精度問題”,但如果繼續追問:

  • 為什麼偏偏是 0.10.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),精度丟失了。

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起討論學習,一起進步。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.