Python四捨五入
如何在輸出時做好四捨五入
這個問題是從 ZeroJudge a647. 投資專家 而來的,這題在考的就是如何解決浮點數不精確導致四捨五入總是出錯的問題。
先來說說什麼問題,看看以下的程式:
print(round(3.15, 1)) # 3.1
print(round(3.16, 1)) # 3.2
print(round(4.15, 1)) # 4.2
print(round(4.16, 1)) # 4.2
3.15 與 4.15 經過 round 的結果竟然不同!有人說這叫四捨六入五成雙,但傑夫老師是沒看到什麼"成雙"的啦!
真正原因不細究,官方有解釋:
Note: The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.
重點是,怎麼解決!
看了很多ZJ的解題說明,大多的方式都是:
if r<0:
r = r - 0.000001 # 如果結果小於 0,減去處理誤差的數值
elif r>0:
r = r + 0.000001 # 如果結果大於 0,加上處理誤差的數值
這個解法是很簡單,但感覺很像是"騙"過去的。
傑夫老師甚至用了 numpy.round() 也無法解決這個問題。還是那句"能不要用除法就不要用"啊!
那結果勒~能不用偷加(或減) 0.00001 的方式解決嗎?
最後,找上了 decimal ,這個模块提供了对快速且正确舍入的十进制浮点运算的支持;此模块旨在支持“无偏差,精确无舍入的十进制算术(有时称为定点数算术)和有舍入的浮点数算术”。(*.這兩句是原文件的簡中翻譯,後面再看到簡字或很彆扭的語句大多是原文摘出)
總之,方法就是:把計算出來的十進位數字轉換成 Decimal 物件,再經由其 quantize 方法來進行四捨五入。
Decimal 物件
首先,來看看怎麼將十進位數字轉換成 Decimal 物件?
Decimal 物件就是能幫你保存好小數值的物件,讓這數字不要被轉換成二進制時搞亂。建立的方法是 Decimal(小數字串),注意要用字串。請看下面例子:
from decimal import Decimal
x=Decimal("1.1")
print(x) #1.1
y=Decimal(1.1)
print(y) #1.100000000000000088817841970012523233890533447265625
也就是說,如果經過計算後的顯示結果是個"有限(沒有二進制誤差)"的小數,那就可以先轉成字串後,再交由 Decimal 物件做四捨五入了。例如 a647 這題中有兩個數字:
from decimal import Decimal
d=8985/1000
dis=Decimal(str(d))
print(d, dis, round(d,2)) #8.985 8.985 8.98
d=-6999/1000
dis=Decimal(str(d))
print(d, dis, round(d,2)) #-6.999 -6.999 -7.0
注意到 8.985 經過 round 會變成 8.98,這就是因為 8.985 其實是以 8.984999999 存在記憶體中;然而 dis 這個 Decimal 物件中的值還是 8.985。
quantize 四捨五入
接下來要四捨五入就簡單了,方法大致如下:
from decimal import Decimal, ROUND_HALF_UP
dis=Decimal('3.41451356').quantize(Decimal('.000') ,ROUND_HALF_UP)
print(dis) #3.415
這樣就可以四捨五入到小數點下第三位了
那就把這題會出現的兩個數字用 quantize 四捨五入,這題要求的是小數點下兩位:
from decimal import Decimal, ROUND_HALF_UP
d=8985/1000
dis=Decimal(str(d)).quantize(Decimal(".00"), ROUND_HALF_UP)
print(d, dis, round(d,2)) #8.985 8.99 8.98
d=-6999/1000
dis=Decimal(str(d)).quantize(Decimal(".00"), ROUND_HALF_UP)
print(d, dis, round(d,2)) #-6.999 -7.00 -7.0
注意到 -7.00 還自動幫忙補上0,真貼心
a647. 投資專家 程式
from decimal import Decimal, ROUND_HALF_UP
n=int(input())
for _ in range(n):
m,p=map(int, input().split())
d=(p-m)/m*100
dis=Decimal(str(d)).quantize(Decimal(".00"), ROUND_HALF_UP)
if d>=10.0 or d<=-7.0: #實際數字做比較
print(f"{dis}% dispose") #顯示四捨五入的數字
else:
print(f"{dis}% keep")
這樣子,就不需要去多加一個小誤差值,也學到利用Decimal來做四捨五入了~