python精確地進行浮點數的四捨五入

作python實驗時碰到這麼一道題:html

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

錯誤示範

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

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

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

>>>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,須要精確到小數點後兩位,那麼就要看小數點後第三位:.net

  1. 若是d小於5,直接捨去
  2. 若是d大於5,直接進位
  3. 若是d等於5:code

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

例如:htm

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

ps:負數會往絕對值更大的方向「入」、絕對值更小的方向「舍」,此處不作具體分析blog

因此,把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行業,但自己水平不高,缺少計算機底層的知識,又在網上瞎寫博客誤導別人,此次吃了垃圾博客的虧,之後搜索時仍是儘可能用英文+谷歌吧!

參考文章:

相關文章
相關標籤/搜索