今天又有一個Python初學者被中文技術博客中的垃圾文章給誤導了。html
這位初學者的問題是:python
在Python中,如何精確地進行浮點數的四捨五入,保留兩位小數?bash
若是你在Google或者百度上搜索,你會發現大量的來自CSDN、百家號、頭條號或者簡書上面的文章講到這一點,可是他們的說法無外乎下面幾種:微信
以下圖所示,懶得吐槽。函數
他們舉的例子爲:測試
>>> round(1.234, 2)
1.23
複製代碼
這種文章,他只演示了四舍
,可是卻沒有演示五入
。因此若是你代碼稍做修改,就會發現有問題:ui
>>> round(11.245, 2)
11.24
複製代碼
這種文章稍微好一點,知道多舉幾個例子:spa
然而這種文章也是漏洞百出,只要你多嘗試幾個數字就會發現問題,在Python 2和Python 3下面,效果是不同的。先來看看Python 2下面的運行效果:翻譯
在Python 2裏面,直接使用round
,1.125
精確到兩位小數後爲1.13
,而1.115
精確到兩位小數後是1.11
。3d
再來看看Python 3下面的效果:
在Python 3下面,1.125
在精確到兩位小數之後是1.12
。
他舉的例子,在Python 3中先放大再縮小,也並不老是正確。
還有一種裝逼貨,文章和先放大再縮小差很少,可是他還知道decimal
這個模塊。
不過他的使用方法,你們看他吧
具體緣由不詳
????
不推薦使用這個方法
???
這種人要先裝個逼,表示本身知道有這樣一個庫,可是用起來發現有問題,並且不知道緣由,因此不建議你們使用。
decimal是專門爲高精度計算用的模塊,他居然說不建議你們使用???
罵完了,咱們來講說,在Python 3裏面,round
這個內置的函數到底有什麼問題。
網上有人說,由於在計算機裏面,小數是不精確的,例如1.115
在計算機中其實是1.1149999999999999911182
,因此當你對這個小數精確到小數點後兩位的時候,實際上小數點後第三位是4
,因此四捨五入,所以結果爲1.11
。
這種說法,對了一半。
由於並非全部的小數在計算機中都是不精確的。例如0.125
這個小數在計算機中就是精確的,它就是0.125
,沒有省略後面的值,沒有近似,它確確實實就是0.125
。
可是若是咱們在Python中把0.125
精確到小數點後兩位,那麼它的就會變成0.12
:
>>> round(0.125, 2)
0.12
複製代碼
爲何在這裏四舍
了?
還有更奇怪的,另外一個在計算機裏面可以精確表示的小數0.375
,咱們來看看精確到小數點後兩位是多少:
>>> round(0.375, 2)
0.38
複製代碼
爲何這裏又五入
了?
由於在Python 3裏面,round
對小數的精確度採用了四捨六入五成雙
的方式。
若是你寫過大學物理的實驗報告,那麼你應該會記得老師講過,直接使用四捨五入,最後的結果可能會偏高。因此須要使用奇進偶舍
的處理方法。
例如對於一個小數a.bcd
,須要精確到小數點後兩位,那麼就要看小數點後第三位:
d
小於5,直接捨去d
大於5,直接進位d
等於5:
d
後面沒有數據,且c爲偶數
,那麼不進位,保留cd
後面沒有數據,且c爲奇數
,那麼進位,c變成(c + 1)d
後面還有非0數字,例如實際上小數爲a.bcdef
,此時必定要進位,c變成(c + 1)關於奇進偶舍,有興趣的同窗能夠在維基百科搜索這兩個詞條:數值修約
和奇進偶舍
。
因此,round
給出的結果若是與你設想的不同,那麼你須要考慮兩個緣由:
1.115
,它的小數點後第三位其實是4
,固然會被捨去。round
採用的進位機制是奇進偶舍
,因此這取決於你要保留的那一位,它是奇數仍是偶數,以及它的下一位後面還有沒有數據。若是要實現咱們數學上的四捨五入,那麼就須要使用decimal模塊。
如何正確使用decimal模塊呢?
看官方文檔,不要看中文垃圾博客!!!
看官方文檔,不要看中文垃圾博客!!!
看官方文檔,不要看中文垃圾博客!!!
不要擔憂看不懂英文,Python已經推出了官方中文文檔(有些函數的使用方法尚未翻譯完成)。
咱們來看一下:docs.python.org/zh-cn/3/lib…
官方文檔給出了具體的寫法:
>>>Decimal('1.41421356').quantize(Decimal('1.000'))
Decimal('1.414')
複製代碼
那麼咱們來測試一下,0.125
和0.375
分別保留兩位小數是多少:
>>> from decimal import Decimal
>>> Decimal('0.125').quantize(Decimal('0.00'))
Decimal('0.12')
>>> Decimal('0.375').quantize(Decimal('0.00'))
Decimal('0.38')
複製代碼
怎麼結果和round
同樣?咱們來看看文檔中quantize
的函數原型和文檔說明:
這裏提到了能夠經過指定rounding
參數來肯定進位方式。若是沒有指定rounding
參數,那麼默認使用上下文提供的進位方式。
如今咱們來查看一下默認上下文中的進位方式是什麼:
>>> from decimal import getcontext
>>> getcontext().rounding
'ROUND_HALF_EVEN'
複製代碼
以下圖所示:
ROUND_HALF_EVEN
實際上就是奇進偶舍
!若是要指定真正的四捨五入,那麼咱們須要在quantize
中指定進位方式爲ROUND_HALF_UP
:
>>> from decimal import Decimal, ROUND_HALF_UP
>>> Decimal('0.375').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.38')
>>> Decimal('0.125').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')
複製代碼
如今看起來一切都正常了。
那麼會不會有人進一步追問一下,若是Decimal接收的參數不是字符串,而是浮點數會怎麼樣呢?
來實驗一下:
>>> Decimal(0.375).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.38')
>>> Decimal(0.125).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')
複製代碼
那是否是說明,在Decimal的第一個參數,能夠直接傳浮點數呢?
咱們換一個數來測試一下:
>>> Decimal(11.245).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('11.24')
>>> Decimal('11.245').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('11.25')
複製代碼
爲何浮點數11.245
和字符串'11.245'
,傳進去之後,結果不同?
咱們繼續在文檔在尋找答案。
官方文檔已經很清楚地說明了,若是你傳入的參數爲浮點數,而且這個浮點值在計算機裏面不能被精確存儲,那麼它會先被轉換爲一個不精確的二進制值,而後再把這個不精確的二進制值轉換爲等效的十進制值
。
對於不能精確表示的小數,當你傳入的時候,Python在拿到這個數前,這個數就已經被轉成了一個不精確的數了。因此你雖然參數傳入的是11.245
,可是Python拿到的其實是11.244999999999...
。
可是若是你傳入的是字符串'11.245'
,那麼Python拿到它的時候,就能知道這是11.245
,不會提早被轉換爲一個不精確的值,因此,建議給Decimal
的第一個參數傳入字符串型的浮點數,而不是直接寫浮點數。
總結,若是想實現精確的四捨五入,代碼應該這樣寫:
from decimal import Decimal, ROUND_HALF_UP
origin_num = Decimal('11.245')
answer_num = origin_num.quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
print(answer_num)
複製代碼
運行效果以下圖所示:
特別注意,一旦要作精確計算,那麼就不該該再單獨使用浮點數,而是應該老是使用Decimal('浮點數')
。不然,當你賦值的時候,精度已經被丟失了,建議全程使用Decimal舉例:
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c)
複製代碼
最後,若是有同窗想知道爲何0.125和0.375能被精確的儲存,而1.11五、11.245不能被精確儲存,請在這篇文章下面留言,若是想知道的同窗多,我就寫一篇文章來講明。
最後的最後,若是英文看不懂,中文文檔也太枯燥,那麼就用掘金吧。掘金的高質量中文文章,比CSDN之流高出一萬倍。
若是文章對你有幫助,請考慮關注個人微信公衆號:未聞Code(ID:istkingname)