9.5 電腦怎麼儲存浮點數
上冊 2.2 說過,記憶體裡只有 0 和 1。整數怎麼存你已經知道(二進位),那 3.75 這種小數呢?
答案的靈感正是 9.2 的科學記號——只是把底數從 10 換成 2:每個浮點數都被寫成 \pm 1.\text{xxx} \times 2^{\text{e}} 的形式(1.\text{xxx} 是二進位的小數)。以 double 的 64 個位元為例,三段各司其職:
| 段落 | 位元數 | 存什麼 |
|---|---|---|
| 正負號(sign) | 1 | 0 是正、1 是負 |
| 指數(exponent) | 11 | 2^{\text{e}} 的那個 \text{e}(小數點「浮」到哪) |
| 尾數(fraction) | 52 | 1.\text{xxx} 小數點後的那串 \text{xxx} |
「浮點數」的名字正來自這個設計:小數點的位置不是固定的,靠指數段「浮動」。這帶來一個重要的取捨——有效數字的「精度」由尾數的 52 位元決定,換算成十進位約 15 \sim 16 位。數字再大、再小,有效數字就這麼多。
¶存不準的數:0.1 的秘密
還有一個更隱晦的限制。十進位的 0.1,換成二進位是無限循環小數 0.0001100110011\ldots_{(2)}——就像 \frac{1}{3} 在十進位是 0.333\ldots 一樣寫不完。尾數只有 52 位元,只能存一個最接近 0.1 的近似值。
不信?用 9.3 的工具逼 cout 把家底掏出來:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
cout << fixed << setprecision(20);
cout << 0.1 << '\n';
return 0;
}
執行結果:
0.10000000000000000555
你寫的是 0.1,電腦存的是 0.10000000000000000555\ldots。平常印出 0.1 只是因為預設只顯示 6 位有效數字,把誤差藏起來了。這個小小的偏差,就是下一節所有誤差現象的根源。
動手試試看:把範例中的 0.1 換成 0.5 和 0.25,觀察輸出。為什麼這兩個數印出來一點誤差都沒有?(提示:0.5 = 2^{-1}、0.25 = 2^{-2},在二進位是「有限小數」。)