浮點數在計算機中表達為二進制(binary)小數。例如:十進制小數:
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...
在任意無限位數值中中止,你可以得到一個近似。
在一個典型的機器上運行 Python,一共有53位的精度來表示一個浮點數,所以當你輸入十進制的0.1?的時候,看到是一個二進制的小數:
0.00011001100110011001100110011001100110011001100110011010
非常接近,但是不完全等于,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 提示符和內置的?repr()?函數選擇一個 17 位精度的數字,0.10000000000000001。從 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(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
~~~
雖然這些數字不可能再更接近它們想要的精確值,round()?函數可以用于在計算之后進行舍入,這樣的話不精確的結果就可以和另外一個相比較了:
~~~
>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
~~~
二進制浮點數計算有很多這樣意想不到的結果。“0.1”的問題在下面”誤差的表示”一節中有準確詳細的解釋。更完整的常見怪異現象請參見?[浮點數的危險](http://www.lahey.com/float.htm)。
最后我要說,“沒有簡單的答案”。也不要過分小心浮點數!Python 浮點數計算中的誤差源之于浮點數硬件,大多數機器上每次計算誤差不超過 2**53 分之一。對于大多數任務這已經足夠了,但是你要在心中記住這不是十進制算法,每個浮點數計算可能會帶來一個新的舍入錯誤。
雖然確實有問題存在,對于大多數平常的浮點數運算,你只要簡單地將最終顯示的結果舍入到你期望的十進制位數,你就會得到你期望的最終結果。str()?通常已經足夠用了,對于更好的控制可以參閱?[格式化字符串語法](http://python.usyiyi.cn/python_341/library/string.html#formatstrings)?中?str.format()?方法的格式說明符。
對于需要精確十進制表示的情況,可以嘗試使用?decimal?模塊,它實現的十進制運算適合會計方面的應用和高精度要求的應用。
fractions?模塊支持另外一種形式的運算,它實現的運算基于有理數(因此像1/3這樣的數字可以精確地表示)。
如果你是浮點數操作的重度使用者,你應該看一下由 SciPy 項目提供的 Numerical Python 包和其它用于數學和統計學的包。參看 。
當你真的?_真_?想要知道浮點數精確值的時候,Python 提供這樣的工具可以幫助你。float.as_integer_ratio()?方法以分數的形式表示一個浮點數的值:
~~~
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
~~~
因為比值是精確的,它可以用來無損地重新生成初始值:
~~~
>>> x == 3537115888337719 / 1125899906842624
True
~~~
float.hex()?方法以十六進制表示浮點數,給出的同樣是計算機存儲的精確值:
~~~
>>> x.hex()
'0x1.921f9f01b866ep+1'
~~~
精確的十六進制表示可以用來準確地重新構建浮點數:
~~~
>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True
~~~
因為可以精確表示,所以可以用在不同版本的 Python(與平臺相關)之間可靠地移植數據以及與支持同樣格式的其它語言(例如 Java 和 C99)交換數據。
另外一個有用的工具是?math.fsum()?函數,它幫助求和過程中減少精度的損失。當數值在不停地相加的時候,它會跟蹤“丟棄的數字”。這可以給總體的準確度帶來不同,以至于錯誤不會累積到影響最終結果的點:
~~~
>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True
~~~
- Python 入門指南
- 1. 開胃菜
- 2. 使用 Python 解釋器
- 2.1. 調用 Python 解釋器
- 2.2. 解釋器及其環境
- 3. Python 簡介
- 3.1. 將 Python 當做計算器
- 3.2. 編程的第一步
- 4. 深入 Python 流程控制
- 4.1. if 語句
- 4.2. for 語句
- 4.3. range() 函數
- 4.4. break 和 continue 語句, 以及循環中的 else 子句
- 4.5. pass 語句
- 4.6. 定義函數
- 4.7. 深入 Python 函數定義
- 4.8. 插曲:編碼風格
- 5. 數據結構
- 5.1. 關于列表更多的內容
- 5.2. del 語句
- 5.3. 元組和序列
- 5.4. 集合
- 5.5. 字典
- 5.6. 循環技巧
- 5.7. 深入條件控制
- 5.8. 比較序列和其它類型
- 6. 模塊
- 6.1. 深入模塊
- 6.2. 標準模塊
- 6.3. dir() 函數
- 6.4. 包
- 7. 輸入和輸出
- 7.1. 格式化輸出
- 7.2. 文件讀寫
- 8. 錯誤和異常
- 8.1. 語法錯誤
- 8.2. 異常
- 8.3. 異常處理
- 8.4. 拋出異常
- 8.5. 用戶自定義異常
- 8.6. 定義清理行為
- 8.7. 預定義清理行為
- 9. 類
- 9.1. 術語相關
- 9.2. Python 作用域和命名空間
- 9.3. 初識類
- 9.4. 一些說明
- 9.5. 繼承
- 9.6. 私有變量
- 9.7. 補充
- 9.8. 異常也是類
- 9.9. 迭代器
- 9.10. 生成器
- 9.11. 生成器表達式
- 10. Python 標準庫概覽
- 10.1. 操作系統接口
- 10.2. 文件通配符
- 10.3. 命令行參數
- 10.4. 錯誤輸出重定向和程序終止
- 10.5. 字符串正則匹配
- 10.6. 數學
- 10.7. 互聯網訪問
- 10.8. 日期和時間
- 10.9. 數據壓縮
- 10.10. 性能度量
- 10.11. 質量控制
- 10.12. “瑞士軍刀”
- 11. 標準庫瀏覽 – Part II
- 11.1. 輸出格式
- 11.2. 模板
- 11.3. 使用二進制數據記錄布局
- 11.4. 多線程
- 11.5. 日志
- 11.6. 弱引用
- 11.7. 列表工具
- 11.8. 十進制浮點數算法
- 12. 接下來?
- 13. 交互式輸入行編輯歷史回溯
- 13.1. 行編輯
- 13.2. 歷史回溯
- 13.3. 快捷鍵綁定
- 13.4. 其它交互式解釋器
- 14. 浮點數算法:爭議和限制
- 14.1. 表達錯誤
- 15. 附錄
- 15.1. 交互模式