C++筆記-浮點數
浮點數
名言:「算錢用浮點,遲早被人扁。」
浮點數是一個很難理解的東西,
這裡特別拿出來說明。
宣告
如果遇到一定要用浮點數的情況,請使用 double
或 long double
的型態,
不要使用 float
!
不要使用 float
!
不要使用 float
!
IEEE 754 與浮點數誤差
現在還沒有完整的方式可以存一個實數,目前電腦的儲存方式是採用 IEEE 754 標準
IEEE754是自20世紀80年代以來現代電腦最廣泛使用的浮點數運算標準,除了浮點數的表示以外,它還定義了關於負0、反常值以及其運算子與例外情況,像是inf
(Infinite)、nan
(Not A Number)這些特殊數值。
我們要先知道如何做進制轉換, 欲將十進位轉成二進位,對於整數部分,就是一直除以2直到商數為0,再依序由下往上取出餘數:
而小數則相反,需要一直乘以2直到變成0為止,且每次的運算都只取小數部分,再依序由上往下取出整數。
假設要轉換 432.1 為二進制,
計算 | 結果 | 餘數 |
---|---|---|
432/2 | 216 | 0 |
216/2 | 108 | 0 |
108/2 | 54 | 0 |
54/2 | 27 | 0 |
27/2 | 13 | 1 |
13/2 | 6 | 1 |
6/2 | 3 | 0 |
3/2 | 1 | 1 |
1/2 | 0 | 1 |
由下往上寫結果就是 110110000
對小數部分
計算 | 結果 | 整數部分 |
---|---|---|
0.1*2 | 0.2 | 0 |
0.2*2 | 0.4 | 0 |
0.4*2 | 0.8 | 0 |
0.8*2 | 1.6 | 1 |
0.6*2 | 1.2 | 1 |
0.2*2 | 0.4 | 0 |
0.4*2 | 0.8 | 0 |
0.8*2 | 1.6 | 1 |
0.6*2 | 1.2 | 1 |
這時你會發現它是無限循環小數,結果為 $0.0\overline{0011}$
最後整數小數合併就是 $110110000.0\overline{0011}$
要如何表示單精度浮點數?
以 $12.625_{10}$ 這個浮點數為例,
- 不管正負號,將 $12.625_10$ 拆成 $12+0.625$
- 分別寫成二進位,即 $1100 + 0.101 = 1100.101$
- 正規化,得到 $1100.101 = 1.100101 \times 2^3$,整數部分不可為0
- Sign 佔 1 格,因為是正數,Sign為 0,如果是負數就是 1 => 0
- Exponent 佔 8 格,次方數為 3,請加上 127 後轉成 8 位數二進位 => 10000010
- Fraction(mantissa) 佔 23 格,把小數部分,也就是 101 填進去,如果不到 23 位,需要將後面補 0 直到滿足 23 位 => 10100000000000000000000
至此我們就完成了 IEEE754 單精度浮點數的轉換,需要注意的是,如果正規化後小數部分超過 23 位(無法整除),就要直接放棄掉多的部分。
我們可以看更多範例:
以 $8.5$ 為例,
- 不管正負號,將 $8.5$ 拆成 $8 + 0.5$
- 分別寫成二進位, $8 + 0.5 = 2^3 + 2^-1 = 1000 + 0.1 = 1000.1$
- 正規化後得到 $1000.1 = 1.0001 * 2^3$
- 因為是正的,Sign填 $0$
- 次方數為 $3$,加上 $127$ 後填入轉成二進位填入Exponent => $10000010$
- 把0001填入Fraction(mantissa)並補滿23位,得到 $00010000000000000000000$
以 $1.1$ 為例,
- 不管正負號,將 $1.1$ 拆成 $1 + 0.1$
- 分別寫成二進位,注意看你會發現這個數在二進位下除不盡, $1 + 0.1 = 2^0 + 2^{-4} + 2^{-5} + 2^{-8} + 2^{-9} + 2^{-12} + 2^{-13} + 2^{-16} + 2^{-17} + 2^{-20} + 2^{-21} + 2^{-23} + … $ $ = 1 + 0.00011001100110011001101 = 1.00011001100110011001101$
- 正規化後得 $1.00011001100110011001101 = 1.00011001100110011001101 * 2^0$
- 正的 Sign 為0
- 次方數為 0,加 127 後二進位為 $01111111$
- 剩下的Mantissa填進去,得到 $00011001100110011001101$
以 $-3$ 為例,
- 不管正負號,將 $3$ 拆成 $3+0.0$
- 分別寫成二進位,$11.0$
- 正規化後得 $1.1 * 2^1$
- 負的 Sign 為 1
- 次方數為 1,加上 127 後二進位為 $10000000$
- Mantissa為 $10000000000000000000000$
強制轉型
可以將型別用小括號刮起來將後面的運算子強制轉型
cout << (double)5/3 << "\n";
四捨五入
有時會遇到需要將答案四捨五入到第 $k$ 位,使用 <iomanip>
控制。
|
|
做完後可觀察到,設定完輸出幾位數後會持續生效直到再次設定。
誤差應對
轉型
將浮點數轉成整數時由於是截斷取整,5.0
可能存成 4.999...
,轉成整數時會被轉成 4
,
為了解決這個問題,可以將它先加上一個極小值再做轉型,這個極小值通常是 $10^{-5}$ 到 $10^{-10}$,視需求決定。
|
|
宣告一個常數 double,值為 $10^{-6}$。
|
|
比較
直接使用 ==
比較浮點數是一件危險的事,因為它們不一定會被精確保存,近似值不能保證它們以相同誤差保存。
解決的辦法是:如果它們很接近就當相等,也就是判斷它們的絕對值是否小於極小值。
載入數學函式庫 <cmath>
,並使用 fabs() 得到浮點數的絕對值
|
|