Python教程:Python浮點算術:爭議和限制

      網友投稿 753 2022-05-30

      Python教程:Python浮點算術:爭議和限制

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

      0.125

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

      0.001

      等于0/2 + 0/4 + 1/8。這兩個小數具有相同的值,唯一真正的區別是第一個是以 10 為基數的小數表示法,第二個則是 2 為基數。

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

      用十進制來理解這個問題顯得更加容易一些。考慮分數 1/3 。我們可以得到它在十進制下的一個近似值

      0.3

      或者,更近似的,:

      0.33

      或者,更近似的,:

      0.333

      以此類推。結果是無論你寫下多少的數字,它都永遠不會等于 1/3 ,只是更加更加地接近 1/3 。

      同樣的道理,無論你使用多少位以 2 為基數的數碼,十進制的 0.1 都無法精確地表示為一個以 2 為基數的小數。 在以 2 為基數的情況下, 1/10 是一個無限循環小數

      0.0001100110011001100110011001100110011001100110011...

      在任何一個位置停下,你都只能得到一個近似值。因此,在今天的大部分架構上,浮點數都只能近似地使用二進制小數表示,對應分數的分子使用每 8 字節的前 53 位表示,分母則表示為 2 的冪次。在 1/10 這個例子中,相應的二進制分數是?3602879701896397?/?2?**?55?,它很接近 1/10 ,但并不是 1/10 。

      大部分用戶都不會意識到這個差異的存在,因為 Python 只會打印計算機中存儲的二進制值的十進制近似值。在大部分計算機中,如果 Python 想把 0.1 的二進制對應的精確十進制打印出來,將會變成這樣

      Python教程:Python浮點算術:爭議和限制

      >>>

      >>>?0.1 0.1000000000000000055511151231257827021181583404541015625

      這比大多數人認為有用的數字更多,因此Python通過顯示舍入值來保持可管理的位數

      >>>

      >>>?1?/?10 0.1

      牢記,即使輸出的結果看起來好像就是 1/10 的精確值,實際儲存的值只是最接近 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 的錯誤,也不是你代碼中的錯誤。 你會在所有支持你的硬件中的浮點運算的語言中發現同樣的情況(雖然某些語言在默認狀態或所有輸出模塊下都不會?顯示?這種差異)。

      想要更美觀的輸出,你可能會希望使用字符串格式化來產生限定長度的有效位數:

      >>>

      >>>?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,將三個 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" 的問題會在下面的“表示性錯誤”一節中更詳細地描述。 請參閱?浮點數的危險性?一文了解有關其他常見意外現象的更詳細介紹。

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

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

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

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

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

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

      >>>

      >>>?x?=?3.14159 >>>?x.as_integer_ratio() (3537115888337719,?1125899906842624)

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

      >>>

      >>>?x?==?3537115888337719?/?1125899906842624 True

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

      >>>

      >>>?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

      15.1. 表示性錯誤

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

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

      為什么會這樣? 1/10 是無法用二進制小數精確表示的。 目前(2000年11月)幾乎所有使用 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**52?<=??2**56?//?10??

      也就是說,56 是唯一的?N?值能令?J?恰好有 53 位。 這樣?J?的最佳可能值就是經過舍入的商:

      >>>

      >>>?q,?r?=?divmod(2**56,?10) >>>?r 6

      由于余數超過 10 的一半,最佳近似值可通過四舍五入獲得:

      >>>

      >>>?q+1 7205759403792794

      這樣在 754 雙精度下 1/10 的最佳近似值為:

      7205759403792794?/?2?**?56

      分子和分母都除以二則結果小數為:

      3602879701896397?/?2?**?55

      請注意由于我們做了向上舍入,這個結果實際上略大于 1/10;如果我們沒有向上舍入,則商將會略小于 1/10。 但無論如何它都不會是?精確的?1/10!

      因此計算永遠不會“看到”1/10:它實際看到的就是上面所給出的小數,它所能達到的最佳 754 雙精度近似值:

      >>>

      >>>?0.1?*?2?**? 553602879701896397.0

      如果我們將該小數乘以 10**55,我們可以看到該值輸出為 55 位的十進制數:

      >>>

      >>>?3602879701896397?*?10?**?55//?2?**?55 1000000000000000055511151231257827021181583404541015625

      這意味著存儲在計算機中的確切數值等于十進制數值 0.1000000000000000055511151231257827021181583404541015625。 許多語言(包括較舊版本的 Python)都不會顯示這個完整的十進制數值,而是將結果舍入為 17 位有效數字:

      >>>

      >>>?format(0.1,?'.17f') '0.10000000000000001'

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

      >>>

      >>>?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 計算

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:開源生態紅似火 華為引領中國力量顯崢嶸
      下一篇:深度解讀 OceanConnect 的 DMP 部分 | 物聯網平臺 獨孤九劍(3)
      相關文章
      亚洲视频日韩视频| 亚洲成年人啊啊aa在线观看| 亚洲国产a级视频| 九月丁香婷婷亚洲综合色| 亚洲中文字幕无码中文| 久久青青草原亚洲av无码app| 在线亚洲97se亚洲综合在线 | 国产亚洲视频在线播放| 激情小说亚洲图片| 亚洲av第一网站久章草| 亚洲日本乱码卡2卡3卡新区| 亚洲手机中文字幕| 亚洲精品中文字幕无乱码| 无码久久精品国产亚洲Av影片| 中文字幕一精品亚洲无线一区| 无码不卡亚洲成?人片| 久久综合亚洲色hezyo| 亚洲中文字幕一二三四区苍井空| 78成人精品电影在线播放日韩精品电影一区亚洲 | 久久亚洲中文无码咪咪爱| 亚洲av无码兔费综合| 亚洲成?v人片天堂网无码| 亚洲色偷拍区另类无码专区| 激情97综合亚洲色婷婷五| 亚洲精品无码乱码成人| 亚洲va久久久噜噜噜久久男同| 亚洲一区二区电影| 亚洲人成毛片线播放| 亚洲中文字幕精品久久| 亚洲AV无码AV男人的天堂不卡| 亚洲AV日韩精品一区二区三区 | 久久亚洲欧洲国产综合| 亚洲va久久久噜噜噜久久| 麻豆亚洲av熟女国产一区二| 国产婷婷综合丁香亚洲欧洲| 亚洲熟女综合一区二区三区| 五月婷婷亚洲综合| 亚洲色大成网站www永久一区 | 67pao强力打造67194在线午夜亚洲| 亚洲国产精品综合久久久 | 亚洲成a人片在线观看无码专区|