Python3 里 7/3 的值為什麼會是 2.3333333333333335,末位為什麼會是 5?
python里7/3的值為什麼會是2.3333333333333335,末位為什麼會是5?
短回答:
因為浮點數的精度問題。
長回答:
7/3 結果是一個無限循環小數,而對於計算機,無限循環小數是無法保存的,要進行截斷處理。
你可能會問四捨五入的話應該是2.3333333333333333才對啊,結尾怎麼會是5呢?
其實,這裡的截斷處理是指在計算機浮點數表示法里進行截斷,以64位浮點數為例,有1位表示正負,11位表示指數,52位表示尾數。
計算機所做的事情,是計算 7/3 的值,直到52位尾數全部佔滿位以後停止,也就是截斷。
7/3 計算後為10.010101010101010101010101010101010101010101010101011,轉化為十進位後就是2.3333333333333335,末尾出現5就不奇怪了。
PS:
在python3中,7/3的計算結果以浮點數來表示,為2.3333333333333335。
在python2中,7/3的計算結果以整數來表示,為2.
最後給題主加把勁兒!
回復細心的 @張博宇 提到的問題:
一、10.01010....011,沒數錯的話似乎是53位數字(反正肯定是奇數位),而不是偶數位?
二、為什麼截斷的末尾是01011,而不是01010?三、1.0/3,結果是0.33333...33,最後一位是3,是不是因為Python的二進位最後截斷是0.01010101...0101?
首先指出一處小錯誤:一中不是53位,是51位。
開始答題:
我們來親手算一下。7/3,在這裡我們暫時把二進位小數點算到53位以更清晰地說明問題:
好戲要開始了,我們把上面這個能看花眼的二進位小數轉化為科學記數法:
注意,由於左移了一位小數點,這時候小數點後有54位。
在64位浮點數中:
- 1位符號為正,表示為『0』;
- 11位指數(小數點位移量)為1,表示為『10000000000』;
- 52位尾數為『0010101010101010101010101010101010101010101010101011『為什麼:尾部多出兩位』10『無法保留,所以0舍1入,在第52進一位,所以第52位的0變為1.
合起來,變為:
這也是計算機在內存中存儲的方法。
那麼讀取的時候就要把它再拆過來:
- 在尾數0010101010101010101010101010101010101010101010101011前面加上『1.』:
- 指數為10000000000,即1,小數點右移1位: 注意此時小數點後有51位,這裡回答了第一個問題為什麼末尾是01011和第二個問題為什麼是奇數。
- 符號為0,數字為正;
轉化為十進位,即2.3333333333333335.
第三個問題:1.0/3:
二進位定點數為:0.1010101010101010101010101010101010101010101010101010
- 符號為正,表示為『0『;
- 指數為-1(0.1010……變為01.010……),表示為』11111111110『;
- 尾數表示為』0101010101010101010101010101010101010101010101010101『注意第52位為1,因為其本身就是1,第52位為0所以截斷時未發生進位。
- 合起來:0111111111100101010101010101010101010101010101010101010101010101
換成十進位為0.3333333333333333.
PS:
感謝 @楊鑫逸 在我忙的時候幫我圓了場。
如果有1000本《哈姆雷特》,那麼就有1000位莎士比亞。
本莎士比亞哦不,本菜鳥所用方法出自弗羅贊《計算機科學導論(第三版)》第三章』數據存儲『,若有錯誤請及時提醒。
感謝 @msoeg 的提醒:
64位浮點數轉為十進位後有效數字為16位,所以第17位是不準確的。
在這裡向知友們提出一個思考題:
0.1+0.2 == 17位有效數字;
而0.1+0.7 == 16位有效數字。
這是為什麼?
十進位如何轉二進位?
有知友發私信問我2.3333...如何轉為10.010101...的,在這裡解答一下疑惑。
首先弄清楚:十進位3在二進位是11,沒錯。但是十進位0.3在二進位就不是0.11了。
在這裡為了更好地說明問題,我們取『22.33』來做實驗。
十進位小數轉化為二進位小數有兩部分:整數部分22和小數部分0.33。
先來處理整數部分『22』:
- 源為22,底為2,目標暫為空。
- 用底除源,商11餘0. 目標為『0』,新源為11.
- 用底除源,商5餘1,目標為『10』,新源為5.
- 用底除源,商2餘1,目標為『110』,新源為2.
- 用底除源,商1餘0,目標為『0110』,新源為1.
- 用底除源,商0餘1,目標為『10110』,結束。
二進位整數部分為『10110』。
再來處理小數部分『0.33』:
- 源為0.33,底為2,目標暫為空,我們把目標位數定為7位。
- 用底乘源,得0.66,目標為『0』,新源為0.66.
- 用底乘源,得1.32,目標為『01』,新源為0.32.
- 用底乘源,得0.64,目標為『010』,新源為0.64.
- 用底乘源,得1.28,目標為『0101』,新源為0.28.
- 用底乘源,得0.56,目標為『01010』,新源為0.56.
- 用底乘源,得1.12,目標為『010101』,新源為0.12.
- 用底乘源,得0.24,目標為『0101010』,結束。
二進位整數部分為0.0101010.
合起來:
得10110.0101010
PS:
此方法出自弗羅贊《計算機科學導論(第三版)》第二章『數字系統『,若有錯誤請及時提醒。
這種技術性回答有那麼多人瀏覽,有點出乎意料。
所以,滿足一下我滴好奇心:你們為什麼點進來看這種『枯燥』的技術問題?
來自系統內存尷尬而不失禮貌的微笑
就是四捨五入。只不過是二進位的四捨五入。學名截斷誤差
這不是python的問題,這個一方面是是實數的問題,另一方面是內存的問題。
實數是無限精度的,而內存是有限的。所以只能以誤差的形式表現出來。
你也可以試一試0.1+0.2這類的,總之只要是按照IEEE754標準來的浮點數極其演算法,都會出現你提的這個問題。
https://en.wikipedia.org/wiki/IEEE_754
浮點數和整數的問題
雙精度本身具有位數精度限制。如果想要精確計算結果,可以自己寫一個有理數類,或者從已有的庫中找有理數類、實數類等更精確的類。
有限的內存無法保存無限的小數位,所以要取一個近似值。
而內存是用二進位保存的,所以0.0000000004是很難表達的,而0.00000005就比較好表達了我是這麼理解的。。。因為二進位的話,小數位其實是用二的負n次存儲的。基本都是末尾和5有關的數字。推薦閱讀: