【Python自學】python 浮點數的誤差以及其解決方式
在Python環境當中,輸入以下的代碼:
(一)#浮點數的誤差
x =
float(0.1+ 0.2)
print(x)
Run的結果:0.30000000000000004
什麼?難道跟我學的不一樣嗎?
再看看另外一個例子:
(二)#浮點數的誤差
x =
float(1-9/10)
print(x)
Run的結果:0.09999999999999998
蛤?根據我粗淺的數學計算能力,1-9/10=1/10,或者說0.1,再怎樣也不會是0.09999999999999998這種詭異的數字。
那不然......我們不計算,看一下直接print(1/10)是什麼答案?
(三)#浮點數的誤差
x =
float(1/10)
print(x)
Run的結果:0.1
???根據(二)(三),所以1-9/10不等於1/10嗎?
這就是所謂的「浮點數誤差」!
進入正題!
為什麼 float 計算上有時候會不準?
原因:float 使用 IEEE 754 二進位浮點數標準來表示數字。
這也不是只有Python會這樣,所有使用 IEEE 754 的語言都這樣(C、Java、JavaScript 都一樣)。
某些十進位數字無法用二進位「剛好表示」。因此像 0.1 + 0.2 在內部儲存時會有微小誤差。
其中道理是這樣的,先來討論「十進位與二進位」
1/3 在十進位中是:0.333333333333...(無限循環)
明明是很簡單的分數,卻表示不完,我們稱之為循環小數。
而0.1 在二進位中也是「無限循環小數」
只是電腦只能存有限位數,只好在可以儲存的極限數後面截斷,誤差就在這裡產生。
舉個例子來說:0.1 換成二進位會變怎樣?
0.0001100110011001100110011001100110011001100110011...(0011無限循環)
這串 0011 會無限重複,就像
0.33333 無限重複一樣。
但電腦只能存 53 位有效位元(IEEE
754 double precision),所以電腦會把它 截成 53 位,最後就會長成下面這樣
0.00011001100110011001100110011001100110011001100110011
(只保留前面一段)
然後python會截斷後再轉回十進位
0.1000000000000000055511151231257827021181583404541015625...
這就是浮點數的誤差來源
那怎麼辦?
最常見的解決方法(共 3 種)
方法一:用 round() 做四捨五入【最常見也最暴力的解法】
result
= round(0.1 + 0.2, 2)
print(result)
Run的結果:0.3
誤差在小數點這麼後面的位置,其實並不影響平常常用的數學運算,只要把後面誤差的部份給四捨五入處理掉,就並無大礙了。
這種用法適合顯示一般結果、日常代碼;但是想也知道,並不適合需要精確計算的情況,像金額帳務、天文計算……等。
方法二:用 Decimal處理
用 Decimal(處理金額/高精度運算最佳)
from
decimal import Decimal
#
Decimal的用法,注意參數是字串型別
a =
Decimal("0.1")
b =
Decimal("0.2")
print(a
+ b)
Run的結果:0.3
那為什麼Decimal可以,float不行?
因為Decimal使用 十進位(base
10)儲存與計算,不是二進位浮點數。當它收到字串時,可以「逐位解析」文字 → 數值 → 精確十進位表示。
也就是說,只要字串裡的數字是我們一般所想的十進位小數,Decimal 就能無誤差地表示。
重點:一定要用字串來建立 Decimal,用float去建立的話就白忙一場了
方法三:用 Fraction(分數計算)
from fractions import Fraction
a = Fraction(1, 10) # 代表 1/10
b = Fraction(2, 10) # 代表 2/10
print(a + b)
Run的結果:3/10
直接用分數計算,不要進到小數的世界裡,就不會有十進位轉二進位所導致的誤差情況。
適合需要保留「精確分數結果」的數學題。這種情況讓我想起一段學習數學的往事,國小的時候,數學應用題的計算結果,往往會用小數表示;然而上了國中之後,數學老師就嚴禁我們用小數表示計算結果,一定要用「分數」表示。
我不確定跟這一篇想表達的「浮點數誤差」有沒有關係,我只是想分享這件事。
留言
張貼留言