0.1+0.2!==0.3,為什麼?

山頭人漢波發表於2022-04-19

先說結論

為什麼不等於?

因為浮點數表示小數的時候有精準度損失

為什麼會有精準度損失

因為計算機硬體儲存資料時,是以二進位制(10101010)形式進行

比如說每個位元組是 8 位,int 型別佔 4 個位元組,也就是 32 位精度;那麼 32 位的計算機精度可以存 2 的 32次方個資料。如下圖:

image

每位上面可以放兩個二進位制資料也就是 0 或者 1;一般最高位上是符號位(1表示負數,0表示正數),所以帶符號的型別資料應該是 31 個 2

2 2 2 ... 2(31個2),加上符號範圍就是 -2147483648 ~ 2147483647;當然也有無符號整形,暫不討論

那麼小數怎麼存呢?小數在計算機當中叫浮點型,JS 最終會由瀏覽器引擎轉成 C++,但是 JS 當中只有一種數值型別,那就是 number,那麼 number 在 C++ 是什麼型別呢;

我們暫且認為它是雙精度型別,也就是 double,C++ 中佔四個位元組,也就是 64 位儲存,整數儲存參考上面即可,重點說說浮點儲存

同樣 64 位可分為三部分,它的制定格式是以 IEEE 754 為標準:

第一部分:符號位(S),佔 1 位即第 63 位;

第二部分:指數域(E),佔 11 位即 52 位到 62 位,含 52 和 62;

第三部分:尾數域(F),佔 52 位即第 0 位到 51 位,含 51;

64-bit

如果將一個小數轉換成二進位制 64 位怎麼表示,以 12.52571 為例

  • 先轉換成二進位制(十進位制轉換成二進位制)(站長工具二進位制轉換

    • 12.52571 => 1100.100001101001010011101110001110010010111000011111
  • 將其小數點向左偏移三位

    • 1.100100001101001010011101110001110010010111000011111 * 2^3

得出結論

  1. 因為是整數,所以符號位 S 是 0;
  2. 因為向左偏移了三位,所以 E = 1023 + 3 = 1026(轉化為二進位制) => 10000000010,有 11 位,不夠前面補 0

    • 為什麼要加1023?為什麼左移是加3,不是減3
  3. 尾數是(F)(小數點後面)100100001101001010011101110001110010010111000011111;

最終表示: 0 10000000010 100100001101001010011101110001110010010111000011111;

上面總長度是63位,差一位,最後面補零,即

0 10000000010 1001000011010010100111011100011100100101110000111110;

那麼12.52571的64位計算機儲存形式就是上面了;

回過頭看 0.1 + 0.2

上面的表達可能有些疑惑,肯定的,畢竟筆者也是參考的(權當筆記,供以後溫習),暫且不表;那麼 0.1 和 0.2 是怎麼轉的

這裡就有一個問題,0.1 和 0.2 轉成二進位制小數點後面是迴圈的

// 0.1 轉化為二進位制
0.0 0011 0011 0011 0011...(0011無限迴圈)
// 0.2 轉化為二進位制
0.0011 0011 0011 0011 0011...(0011無限迴圈)

由於尾數只有52位(52位之後的被計算機截掉了)

E = -4; F =1001100110011001100110011001100110011001100110011010 (52位)
E = -3; F =1.1001100110011001100110011001100110011001100110011010 (52位)

要讓兩個數相加,首先E需要相同,於是得出下面

E = -3; F =0.1100110011001100110011001100110011001100110011001101 (52位) //多餘位截掉
E = -3; F =1.1001100110011001100110011001100110011001100110011010 (52位)

上面兩個相加得出

E = -3; F = 10.0110011001100110011001100110011001100110011001100111
-------------------------------------------------------------------
E = -2; F = 1.00110011001100110011001100110011001100110011001100111

得出的結論就是

2^-2 * 1.00110011001100110011001100110011001100110011001100111

這個值轉換成真值,結果為: 0.30000000000000004

如何做到精準度

JavaScript 的型別 bigInt (ES8)中

TypeScript 也有這樣的型別

有解決精準度問題的 big.js、bigInt 庫

同樣有精準度缺失的語言

python

總結

因為 JavaScript 到最後會轉換為 C++ 去執行

在 IEEE754 標準中常見的浮點數數值表示有:單精準度(32位)和雙精準度(64位),JS 採用的是後者。浮點數與整數不同,一個浮點數既包含整數部分,又包含小數部分,因為其表示法的不同,需要分析為整數和小數部分,然後相加得到結果。0.1 和 0.2 先轉成二進位制,在轉換為同一維度計算,得到二進位制後,再轉換為十進位制後,就成了0.30000000000000004

相關文章