網頁

2019年4月15日 星期一

Floating Point Arithmetic: Issues and Limitations

在Python document的Table of Contents第15章有討論一個浮點數經典問題.1 + .2 != .3,這個問題在其他程式語言也會出現,還有國外網站特別為此問題架設,網址是http://0.30000000000000004.com/,以下開始介紹這個有趣問題。

浮點數在計算機硬件中表示為以 2 為基數(二進制)的小數。舉例而言,十進制的小數



等於 1/10 + 2/100 + 5/1000 ,同理,二進制的小數



不幸的是,大多數的十進制小數都不能精確地表示為二進制小數。這導致在大多數情況下,你輸入的十進制浮點數都只能近似地以二進制浮點數形式儲存在計算機中。

請注意這種情況是二進制浮點數的本質特性:它不是 Python 的錯誤,也不是你代碼中的錯誤。你會在所有支持你的硬件中的浮點運算的語言中發現同樣的情況(雖然某些語言在默認狀態或所有輸出模塊下都不會顯示這種差異)。

“對此問題並無簡單的答案。” 但是也不必過於擔心浮點數的問題! Python 浮點運算中的錯誤是從浮點運算硬件繼承而來,而在大多數機器上每次浮點運算得到的 2**53 數碼位都會被作為 1 個整體來處理。這對大多數任務來說都已足夠,但你確實需要記住它並非十進制算術,且每次浮點運算都可能會導致新的捨入錯誤。

雖然病態的情況確實存在,但對於大多數正常的浮點運算使用來說,你只需簡單地將最終顯示的結果舍入為你期望的十進制數值即可得到你期望的結果。 str() 通常已足夠,對於更精度的控制可參看 Format String Syntax 中 str.format() 方法的格式描述符。

對於需要精確十進製表示的使用場景,請嘗試使用 decimal 模塊,該模塊實現了適合會計應用和高精度應用的十進制運算。

另一種形式的精確運算由 fractions 模塊提供支持,該模塊實現了基於有理數的算術運算(因此可以精確表示像 1/3 這樣的數值)。

如果你是浮點運算的重度用戶,你應該看一下數值運算 Python 包 NumPy 以及由 SciPy 項目所提供的許多其它數學和統計運算包。參見 <https://scipy.org>。

Python 也提供了一些工具,可以在你真的 想要 知道一個浮點數精確值的少數情況下提供幫助。例如 float.as_integer_ratio() 方法會將浮點數表示為一個分數:


由於這是一個精確的比值,它可以被用來無損地重建原始值:


float.hex() 方法會以十六進制(以 16 為基數)來表示浮點數,同樣能給出保存在你的計算機中的精確值:


這種精確的十六進製表示法可被用來精確地重建浮點值:


 由於這種表示法是精確的,它適用於跨越不同版本(平台無關)的 Python 移植數值,以及與支持相同格式的其他語言(例如 Java 和 C99)交換數據.

另一個有用的工具是 math.fsum() 函數,它有助於減少求和過程中的精度損失。它會在數值被添加到總計值的時候跟踪“丟失的位”。這可以很好地保持總計值的精確度, 使得錯誤不會積累到能影響結果總數的程度:




本小節將詳細解釋 "0.1" 的例子,並說明你可以怎樣親自對此類情況進行精確分析。假定前提是已基本熟悉二進制浮點表示法。

表示性錯誤 是指某些(其實是大多數)十進制小數無法以二進制(以 2 為基數的計數制)精確表示這一事實造成的錯誤。這就是為什麼 Python(或者 Perl、C、C++、Java、Fortran 以及許多其他語言)經常不會顯示你所期待的精確十進制數值的主要原因。

為什麼會這樣? 1/10 是無法用二進制小數精確表示的。目前(2000年11月)幾乎所有使用 IEEE-754 浮點運算標準的機器以及幾乎所有系統平台都會將 Python 浮點數映射為 IEEE-754 “雙精度類型”。 754 雙精度類型包含 53 位精度。

如果我們將0.1用Decimal輸出,我們可以看到該值輸出為 55 位的十進制數,但許多語言(包括較舊版本的 Python)都不會顯示這個完整的十進制數值,而是將結果舍入為 17 位有效數字:



fractions 和 decimal 模塊可令進行此類計算更加容易:



最後,如果我們要怎麼讓.1 + .2 = .3





參考
https://docs.python.org/3.8/tutorial/floatingpoint.html
https://docs.python.org/zh-cn/3.8/tutorial/floatingpoint.html

沒有留言:

張貼留言