數值穩定與誤差
(0.1 + 0.2) == 0.3
這個看起來非常理所當然的等式,然而由於計算機對浮點數的儲存方式,在很多程式語言當中這個等式並不成立,也就是答案為 false
。
不過另外一個問題是,為什麼 (0.5 + 0.25) == 0.75
這個等式又會成立呢?
在做浮點數運算時多少都會出現誤差。接下來我們討論為什麼會有誤差,以及在計算上可以做哪些事情來避免。
浮點數在計算機的儲存方式
浮點數在電腦當中會以 bit 排列表示,由 IEEE754 所規範。其中單精度浮點數為 32bit;雙精度則為 64bit。 其中分成表示正負數的符號位、小數部、指數部。
- 符號位(1bit):0 為正數;1 為負數
- 小數部
- 指數部
型別 | 大小 | 指數部 | 小數部 |
---|---|---|---|
單精度 | 32bit | 8bit | 23bit |
雙精度 | 64bit | 11bit | 52bit |
以單精度為例,由於指數部為 8bit,可以表示的範圍為 0 ~ 255,但因為需要表示負數,在 IEEE754 的規範當中需要加上一個偏移量,才是最後儲存在指數部的數字。以單精度來說,偏移量為 127。
舉例來說,-14.75 在浮點數的表示會是:
- 符號位:負數,所以為
1
- 指數部:先將 14.75 用二進位表示
1110.11
,正規化後為1.11011* 2^3
,指數部為 3 加上 127 後為 130,二進位表示為10000010
- 小數部:
11011
,其餘的位數為0
所以 -14.75 的浮點數表示會是:1 10000010 11011000000000000000000
從這邊我們也可以知道誤差從何而來,由於指數為負數後,得到的數字都是 0 < x < 1
之間,因此如果是無法透過 2^-x
表示的數字,就只能盡可能趨近而無法做到相同。開頭提到的 0.5 + 0.25
,由於這些數字可以寫成 2^-1+ 2^-2
,因此計算上不會出現誤差。
為什麼要這樣儲存?
使用科學記數表示(Scientific notation)可以幫助我們處理各種 scale 的數字,並且保持一定的精度。像是 0.00000012345
和 1234567890000
,透過科學記數可以表示為 1.2345E-7
和 1.23456789E12
。在計算機當中通常是使用浮點數格式儲存,跟科學記數類似,只是將十進位改成了二進位。
實數有無限多個,然而電腦的儲存空間有限,因此不管如何儲存都一定會有誤差。我們能做的是在精度與可表達的數字範圍作 trade-off。
有效數字
有效數字是幫助我們判斷精度的指標。像是 0.001
或 0.0135
這些數字,我們可以寫作 跟 。這時 0.001
的有效數字為 1 位,1.35
的有效數字為 3 位。有效數字位數越多,精度越高。
在維基百科有個簡單的判斷規則:
- 所有非零數字都是有效的
- 非零數字間的零都是有效的
- 前綴零始終無效
- 對於需要小數點的數,後綴零(最後一個非零數字後的零)是有效的
- 對於不需要小數點的數,後綴零可能有效也可能無效。需要根據額外的符號或者誤差訊息決定。
Cancellation of significant digits
兩個絕對值非常相近的浮點數,在做減法運算時,因為大部分的數字相同,會剩下很多 0,導致有效數字減少,這個現象就叫做 cancellation of significant digits。
舉例來說:(1.234567890 - 1.234567889)
,雖然結果為 0.000000001
,但在精度不足的情況下有可能變為 0
。
這在數值計算當中是需要特別注意的事:舉例來說倍角與半角公式
當角度比較低的時候,由於 cos 相當接近 1,因此與 1 相減很容易出現有效數字遺失的問題。舉例來說:角度為 1 度時,使用倍角公式:(有效數字為 6 位數的情況下)
如果採用右邊的公式直接查表的話:
可以發現兩者的誤差相當明顯。在數值計算中要特別小心。要避免有效數字遺失有幾個方法:
-
盡可能避免絕對值相近的兩個數做運算
-
改用其他公式計算(例如上述的倍角公式)
- 其實就是避免讓絕對值相近的兩個數做運算
-
提高精度
結論
比較有經驗的工程師應該多少都知道浮點數運算會有誤差,也知道 0.1 + 0.2 != 0.3
的原因為何,這篇文章深入探討了小數的儲存方式、IEEE754、以及有效數字的遺失。
倍角公式應該是在高中三角函數時常常出現的題目,對我來說就是套套公式而已。
然而實際的應用都是透過電腦在算的,現實生活也不會到處都是 30 度 60 度這種好算的數字,老師也不會跟你說倍角公式會有有效數字的問題。