如何在python中精確地進行浮點數的四捨五入

在python試題中碰到這麼一道題:


輸入三個浮點數,求它們的平均值並保留 1 位小數,對小數後第二位數進行四捨五入,最後輸出結果

錯誤示範

因爲涉及到四捨五入,隨便搜了一下,發現了好多博客都用round(),就直接拿來用了

round(1.555, 2)    // 對小數後第二位數進行四捨五入
# 1.55

但是當我測試時發現這個四捨五入有點啊!比如:

>>>round(0.5)
0
>>>round(1.5)
2

原因

和想的不一樣啊,然後我就去找python的官方文檔,它是這麼描述的:

round(values, ndigits),values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done toward the even choice.
值四捨五入到最接近的10倍冪減去ndigits;如果兩個倍數相等,則四捨五入到偶數。

什麼意思?

我嘗試了幾個例子才明白是怎麼一回事。
如果你寫過大學物理的實驗報告,那麼你應該會記得老師講過,直接使用四捨五入,最後的結果可能會偏高。所以需要使用四捨六入五成雙的處理方法。

例如對於一個小數a.bcd,需要精確到小數點後兩位,那麼就要看小數點後第三位:

  1. 如果d小於5,直接捨去
  2. 如果d大於5,直接進位
  3. 如果d等於5:

    1. d後面沒有數據,且c爲偶數,那麼不進位,保留c
    2. d後面沒有數據,且c爲奇數,那麼進位,c變成(c + 1)
    3. 如果d後面還有非0數字,例如實際上小數爲a.bcdef,此時一定要進位,c變成(c + 1)

例如:

1. 0.345,4是偶數,所以5捨去,結果0.34
2. 0.3451,5後面還有數,則4進位,結果0.35

ps:負數會往絕對值更大的方向“入”、絕對值更小的方向“舍”,此處不做具體分析

所以,把round()當成四捨五入並不是十分準確的

 

一處小陷井

但是,到這裏並沒有完,當我又換了一組數據測試時,發現了問題:

>>>round(0.645,2) # 按照上述舍入規則,應該是0.64,但結果卻是0.65

這裏就涉及到python的浮點數存儲了,python採用IEEE754標準存儲浮點數的,所以當我輸入0.645後,底層存儲的其實是0011111111100100101000111101011100001010001111010111000010100100,也即十進制的0.645000000000000017763568394002504646778106689453125,離0.65更近。

 

正確姿勢

從上可知,round()對浮點數四捨五入存在舍入規則和浮點數存儲的問題
對於浮點數運算,python提供了Decimal(小數)模塊來讓小數的運算更貼近我們人正常計算的習慣。

import decimal

# 修改舍入方式爲四捨五入
decimal.getcontext().rounding = "ROUND_HALF_UP"

# 使用字符串來儲存小數不會有精度誤差,Decimal可以正確處理這種方法表示的數字
decimal.Decimal("0.645").quantize(decimal.Decimal("0.00"))

或者爲了避免浮點數儲存導致精度損失,乾脆全部都用字符串來儲存小數,如下:

from decimal import Decimal
a = Decimal('0.655') + Decimal('0.345')
b = 0.655 + 0.345
# a = 1.000
# b = 1.0

最後附上一開始的問題吧:

# 輸入三個浮點數,求它們的平均值並保留 1 位小數,對小數後第二位數進行四捨五入,最後輸出結果
import decimal
numbers = list(map(decimal.Decimal, input().split(',')))
# 修改舍入方式爲四捨五入
decimal.getcontext().rounding = "ROUND_HALF_UP"

# 計算平均數
result = decimal.Decimal(sum(numbers) / numbers.__len__())

# 使用字符串來儲存小數不會有精度誤差,Decimal可以正確處理這種方法表示的數字
roundResult = decimal.Decimal(str(result)).quantize(decimal.Decimal("0.00"))

print(roundResult)

>>>1.535,1.545,1.555 # 平均數爲1.545
1.5                  # 保留一位小數, 對小數點後第二位進行四捨五入

總結

  1. 關於浮點數運算和四捨五入的問題,以前在學習C語言時就遇到了,但當時並不清楚浮點數的存儲和運算,也沒有找到一個合適的解決方法,這學期學習了計算機組成,才把這個問題算是比較清楚地給解決了。
  2. 現在越來越能感覺到python語言的大火,好多別的行業的人也通過python轉到了IT行業,但本身水平不高,缺乏計算機底層的知識,又在網上瞎寫博客誤導別人,這次吃了垃圾博客的虧,以後搜索時還是儘量用英文+谷歌吧!

參考文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章