無論在數學上,或是生活運用都很常見到四捨五入,就好像打折或是計算服務費,當有小數點時,就會想要用四捨五入來取到整數。
而在Python 是怎樣使用呢?該不該使用round 呢?還有在計算時有什麼細節呢?
Python版本:
- 3.8.5
- 3.9.2
Round
Python 中內建有提供一個round的功能(build-in function),用來進行四捨五入的計算,在網路上也很多教學網站也是使用這個。但這個有一些小問題,讓我們探究竟。
round:整數
首先定義好個數字,分別是0.5
和1.5
,然後用round
功能,取得小數點第一位,也就是直接用round(數字)
,不用輸入第二個參數。
case_a = 0.75
round_case_a = round(case_a, 1)
print(f"case_a: {case_a}, round: {round_case_a}")
case_b = 0.85
round_case_b = round(case_b, 1)
print(f"case_b: {case_b}, round: {round_case_b}")
但是看到結果分別是:case_a: 0.75, round: 0.8
和case_b: 0.85, round: 0.8
,看起來怪怪的,0.75 四捨五入後是0.8 沒錯,但而0.85 四捨五入後是也是0.8,到底是怎麼回事?
Return number rounded to ndigits precision after the decimal point. If ndigits is omitted or is
None
, it returns the nearest integer to its input.
到底什麼是最近的數字呢?文件又繼續說到:
For the built-in types supporting
round()
, 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(so, for example, bothround(0.5)
andround(-0.5)
are0
, andround(1.5)
is2
).
所以我們可以知道,使用「round
」他會找到最近的整數,這並不是bug(官方說的,不是我!)。這其實是每個程式語言都會遇到的問題,這造成早期程式計算中有很多溢位相關的問題,但這不在話下。參考官方文件,可以知道在進行小數點的時候,會存在一個極限,導致計算會失準。因為先天的限制,所以Python 在round
會有個特性(直接說答案XD),也就是Python 會在整數時候自己去判斷進位到最近的偶數。
廢話不多說,直接看例子:
case_5 = 35
case_6 = 45
print(f"case_5: {case_5}, round: {round(case_5, -1)}") # round: 40
print(f"case_6: {case_6}, round: {round(case_6, -1)}") # round: 40
case_7 = 75
case_8 = 85
print(f"case_7: {case_7}, round: {round(case_7, -1)}") # round: 80
print(f"case_8: {case_8}, round: {round(case_8, -1)}") # round: 80
case_9 = 150
case_10 = 250
print(f"case_9: {case_9}, round: {round(case_9, -2)}") # round: 200
print(f"case_10: {case_10}, round: {round(case_10, -2)}") # round: 200
結果都是往偶數攏:
case_5: 35, round: 40
case_6: 45, round: 40
case_7: 75, round: 80
case_8: 85, round: 80
case_9: 150, round: 200
case_10: 250, round: 200
這跟我們認知的四捨五入真的不同耶!
那小數點的狀況如何呢?
round:小數點
浮點數看會不會也跟整數一樣,往偶數靠攏:
case_f_1 = 0.75
round_case_1 = round(case_f_1, 1)
print(f"case_f_1: {case_f_1}, round: {round_case_1}") # round: 0.8
case_f_2 = 0.85
round_case_2 = round(case_f_2, 1)
print(f"case_f_2: {case_f_2}, round: {round_case_2}") # round: 0.8
以範例來說,0.75
和0.85
四捨五入後應該都會是0.8
,結果是沒錯。
再看看更多數字,結果發現不如預期:
case_f_3 = 0.075
case_f_4 = 0.085
print(f"case_f_3: {case_f_3}, round: {round(case_f_3, 2)}") # round: 0.07
print(f"case_f_4: {case_f_4}, round: {round(case_f_4, 2)}") # round: 0.09
0.075
和0.085
在四捨五入後,分別是0.07
與0.09
,一個進位一個不進位,而且也不是向整數靠齊,到底為什麼呢?
這跟上面說的,在有小數的處理中程式都不是完美的,用format(case_f_3, '.20f')
顯示0.75
實際上是:0.07499999999999999722
,而0.85
則是0.08500000000000000611
,所以0.75
才不會進位。
小數點真的是另外的世界。
所以需要用另外的方法來進行四捨五入。
Decimal
可使用decimal
模組,就可以精準的轉換。需要先import 套件進來:
from decimal import Decimal, ROUND_HALF_UP
首先要把我們的數字變成字串,然後在用quantize
來轉換:
str_decimal = str(0.055)
case_d_1 = Decimal(str_decimal).quantize(Decimal(".00"), ROUND_HALF_UP)
print(f"case decimal 1: {case_d_1}") # case decimal 1: 0.06
str_decimal_2 = str(0.065)
case_d_2 = Decimal(str_decimal_2).quantize(Decimal(".00"), ROUND_HALF_UP)
print(f"case decimal 2: {case_d_2}") # case decimal 2: 0.07
str_decimal_3 = str(0.075)
case_d_3 = Decimal(str_decimal_3).quantize(Decimal(".00"), ROUND_HALF_UP)
print(f"case decimal 3: {case_d_3}") # case decimal 3: 0.08
str_decimal_4 = str(0.085)
case_d_4 = Decimal(str_decimal_4).quantize(Decimal(".00"), ROUND_HALF_UP)
print(f"case decimal 4: {case_d_4}") # case decimal 4: 0.09
四捨五入的結果符合我們的預期。
另外作法
網路上也有其他作法,就是要把小數轉成整數,然後再用「無條件進位」(math.cell()
)或是「無條件捨去」(math.floor()
)來達到四捨五入之目的,接著再除回去。但不推薦此作法,因為需要自行判斷是要使用無條件進位還是無條件捨去,這應該是由程式判斷,因此只紀錄有這方法,就不推薦了。
參考資料
~Copyright by Eyelash500~
IT技術文章:EY*研究院
iT邦幫忙:eyelash*睫毛
Blog:睫毛*Relax
Facebook:睫毛*Relax