### 14. 浮點數運算:問題和局限
浮點數在計算機硬件中表示為以 2 為底(二進制)的小數。例如,十進制小數
~~~
0.125
~~~
是1/10 + 2/100 + 5/1000 的值,同樣二進制小數
~~~
0.001
~~~
是 0/2 + 0/4 + 1/8 的值。這兩個小數具有相同的值,唯一真正的區別是,第一個小數是十進制表示法,第二個是二進制表示法。
不幸的是,大多數十進制小數不能完全用二進制小數表示。結果是,一般情況下,你輸入的十進制浮點數僅由實際存儲在計算機中的近似的二進制浮點數表示。
這個問題在十進制情況下很容易理解。考慮分數 1/3,你可以用十進制小數近似它:
~~~
0.3
~~~
或者更接近的
~~~
0.33
~~~
或者再接近一點的
~~~
0.333
~~~
等等。無論你愿意寫多少位數字,結果永遠不會是精確的 1/3,但將會越來越好地逼近 1/3。
同樣地,無論你使用多少位的二進制數,都無法確切地表示十進制值 0.1。1/10 用二進制表示是一個無限循環的小數。
~~~
0.0001100110011001100110011001100110011001100110011...
~~~
在任何有限數量的位停下來,你得到的都是近似值。今天在大多數機器上,浮點數的近似使用的小數以最高的53位為分子,2的冪為分母。至于1/10這種情況,其二進制小數是3602879701896397/2**55,它非常接近但不完全等于1/10真實的值。
由于顯示方式的原因,許多使用者意識不到是近似值。Python 只打印機器中存儲的二進制值的十進制近似值。在大多數機器上,如果 Python 要打印 0.1 存儲的二進制的真正近似值,將會顯示
~~~
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
~~~
這么多位的數字對大多數人是沒有用的,所以 Python 顯示一個舍入的值
~~~
>>> 1 / 10
0.1
~~~
只要記住即使打印的結果看上去是精確的1/10,真正存儲的值是最近似的二進制小數。
例如,數字0.1、0.10000000000000001 和 0.1000000000000000055511151231257827021181583404541015625 都以3602879701896397/2**55為近似值。因為所有這些十進制數共享相同的近似值,在保持恒等式eval(repr(x))==x的同時,顯示的可能是它們中的任何一個。
現在從Python 3.1 開始,Python(在大多數系統上)能夠從這些數字當中選擇最短的一個并簡單地顯示0.1。
注意,這是二進制浮點數的自然性質:它不是 Python 中的一個 bug ,也不是你的代碼中的 bug。你會看到所有支持硬件浮點數算法的語言都會有這個現象(盡管有些語言默認情況下或者在所有輸出模式下可能不會*顯示* 出差異)。
為了輸出更好看,你可能想用字符串格式化來生成固定位數的有效數字:
~~~
>>> format(math.pi, '.12g') # give 12 significant digits
'3.14159265359'
>>> format(math.pi, '.2f') # give 2 digits after the point
'3.14'
>>> repr(math.pi)
'3.141592653589793'
~~~
認識到這,在真正意義上,是一種錯覺是很重要的:你在簡單地舍入真實機器值的*顯示*。
例如,既然0.1不是精確的1/10,3個0.1的值相加可能也不會得到精確的0.3:
~~~
>>> .1 + .1 + .1 == .3
False
~~~
另外,既然0.1不能更接近1/10的精確值而且0.3不能更接近3/10的精確值,使用[round()](# "round")函數提前舍入也沒有幫助:
~~~
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
~~~
雖然這些數字不可能再更接近它們想要的精確值,[round()](# "round")函數可以用于在計算之后進行舍入,這樣的話不精確的結果就可以和另外一個相比較了:
~~~
>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
~~~
二進制浮點數計算有很多這樣意想不到的結果。“0.1”的問題在下面"誤差的表示"一節中有準確詳細的解釋。更完整的常見怪異現象請參見[浮點數的危險](http://www.lahey.com/float.htm)。
最后我要說,“沒有簡單的答案”。也不要過分小心浮點數!Python 浮點數計算中的誤差源之于浮點數硬件,大多數機器上每次計算誤差不超過 2**53 分之一。對于大多數任務這已經足夠了,但是你要在心中記住這不是十進制算法,每個浮點數計算可能會帶來一個新的舍入錯誤。
雖然確實有問題存在,對于大多數平常的浮點數運算,你只要簡單地將最終顯示的結果舍入到你期望的十進制位數,你就會得到你期望的最終結果。[str()](# "str")通常已經足夠用了,對于更好的控制可以參閱[*格式化字符串語法*](#)中[str.format()](# "str.format")方法的格式說明符。
對于需要精確十進制表示的情況,可以嘗試使用[decimal](# "decimal: Implementation of the General Decimal Arithmetic Specification.")模塊,它實現的十進制運算適合會計方面的應用和高精度要求的應用。
[fractions](# "fractions: Rational numbers.")模塊支持另外一種形式的運算,它實現的運算基于有理數(因此像1/3這樣的數字可以精確地表示)。
如果你是浮點數操作的重度使用者,你應該看一下由SciPy項目提供的Numerical Python包和其它用于數學和統計學的包。參看<[http://scipy.org](http://scipy.org)>。
當你真的*真*想要知道浮點數精確值的時候,Python 提供這樣的工具可以幫助你。[float.as_integer_ratio()](# "float.as_integer_ratio")方法以分數的形式表示一個浮點數的值:
~~~
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
~~~
因為比值是精確的,它可以用來無損地重新生成初始值:
~~~
>>> x == 3537115888337719 / 1125899906842624
True
~~~
[float.hex()](# "float.hex")方法以十六進制表示浮點數,給出的同樣是計算機存儲的精確值:
~~~
>>> x.hex()
'0x1.921f9f01b866ep+1'
~~~
精確的十六進制表示可以用來準確地重新構建浮點數:
~~~
>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True
~~~
因為可以精確表示,所以可以用在不同版本的Python(與平臺相關)之間可靠地移植數據以及與支持同樣格式的其它語言(例如Java和C99)交換數據。
另外一個有用的工具是[math.fsum()](# "math.fsum")函數,它幫助求和過程中減少精度的損失。當數值在不停地相加的時候,它會跟蹤“丟棄的數字”。這可以給總體的準確度帶來不同,以至于錯誤不會累積到影響最終結果的點:
~~~
>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True
~~~
### 14.1. 二進制表示的誤差
這一節將詳細解釋“0.1”那個示例,并向你演示對于類似的情況自已如何做一個精確的分析。假設你已經基本了解浮點數的二進制表示。
*二進制表示的誤差* 指的是這一事實,一些(實際上是大多數) 十進制小數不能精確地用二進制小數表示。這是為什么 Python(或者 Perl、 C、 C++、 Java、 Fortran和其他許多語言)通常不會顯示你期望的精確的十進制數的主要原因。
這是為什么?1/10 和 2/10 不能用二進制小數精確表示。今天(2010 年 7 月)幾乎所有的機器都使用 IEEE-754 浮點數算法,幾乎所有的平臺都將 Python 的浮點數映射成 IEEE-754“雙精度浮點數”。754 雙精度浮點數包含 53 位的精度,所以輸入時計算機努力將 0.1 轉換為最接近的 *J*/2***N* 形式的小數,其中*J* 是一個 53 位的整數。改寫
~~~
1 / 10 ~= J / (2**N)
~~~
為
~~~
J ~= 2**N / 10
~~~
回想一下?*J*?有 53 位(>=2**52但<2**53),所以 *N* 的最佳值是 56:
~~~
>>> 2**52 <= 2**56 // 10 < 2**53
True
~~~
即 56 是 *N* ?保證 *J* 具有 53 位精度的唯一可能的值。*J* 可能的最佳值是商的舍入:
~~~
>>> q, r = divmod(2**56, 10)
>>> r
6
~~~
由于余數大于 10 的一半,最佳的近似值是向上舍入:
~~~
>>> q+1
7205759403792794
~~~
因此在 754 雙精度下 1/10 的最佳近似是:
~~~
7205759403792794 / 2 ** 56
~~~
分子和分母都除以2將小數縮小到:
~~~
3602879701896397 / 2 ** 55
~~~
請注意由于我們向上舍入,這其實有點大于 1/10;如果我們沒有向上舍入,商數就會有點小于 1/10。但在任何情況下它都不可能是*精確的* 1/10!
所以計算機從來沒有"看到"1/10: 它看到的是上面給出的精確的小數,754 雙精度下可以獲得的最佳的近似了:
~~~
>>> 0.1 * 2 ** 55
3602879701896397.0
~~~
如果我們把這小數乘以 10**55,我們可以看到其55位十進制數的值:
~~~
>>> 3602879701896397 * 10 ** 55 // 2 ** 55
1000000000000000055511151231257827021181583404541015625
~~~
也就是說存儲在計算機中的精確數字等于十進制值 0.1000000000000000055511151231257827021181583404541015625。許多語言(包括舊版本的Python)會把結果舍入到17位有效數字,而不是顯示全部的十進制值:
~~~
>>> format(0.1, '.17f')
'0.10000000000000001'
~~~
[fractions](# "fractions: Rational numbers.") 和 [decimal](# "decimal: Implementation of the General Decimal Arithmetic Specification.") 模塊使得這些計算很簡單:
~~~
>>> from decimal import Decimal
>>> from fractions import Fraction
>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> format(Decimal.from_float(0.1), '.17')
'0.10000000000000001'
~~~
- Python 2 教程
- 1. 吊吊你的胃口
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限
- Python 2 標準庫
- 1. 引言
- 2. 內建函數
- 3. 不太重要的內建函數
- 4. 內建的常量
- 5. 內建的類型
- 6. 內建的異常
- 7. String Services
- 8. Data Types
- 9. Numeric and Mathematical Modules
- 10. File and Directory Access
- 11. Data Persistence
- 13. File Formats
- 14. Cryptographic Services
- 15. Generic Operating System Services
- 16. Optional Operating System Services
- 17. Interprocess Communication and Networking
- 18. Internet Data Handling
- 20. Internet Protocols and Support
- 26. Debugging and Profiling
- 28. Python Runtime Services
- Python 2 語言參考
- 1. 簡介
- 2. 詞法分析
- 3. 數據模型
- 4. 執行模型
- 5. 表達式
- 6. 簡單語句
- 7. 復合語句
- 8. 頂層的組件
- 9. 完整的語法規范
- Python 3 教程
- 1. 引言
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限