處理數據遇到的小坑,紀錄一下~
之前用 python 處理小數點四捨五入,很慣性地就用 round 這個內建函數,但當我輸入以下數值、想要四捨五入到小數點後一位時,就發現不太妙:
# 輸出結果:
3.1
3.2
4.2
4.2
慘~這樣我算的數據不就都全錯了QQ
所以到底花生省魔術?
具體原因跟 python 儲存 float浮點數的格式有關,其實電腦儲存數字都是用二進制的方式,但是對於許多十進制的小數點、大部分都無法以精準的浮點數做儲存,電腦只會給它一個非常接近的近似值,我們使用 python 的內建函數 format 來查看:
# 打印結果
3.14999999999999991118215802998747676610946655273438
4.15000000000000035527136788005009293556213378906250
從上面的結果可以看出,在電腦裡面儲存 3.15
並不是我們肉眼看到的 3.15 XDD,而是 3.1499999...
。因此給 round 函數做判斷要四捨五入至小數點後一位時,當然不會如預期地出現 3.2。
解決方法(2023.05.31 更新)
在 Python 若要進行精確的浮點數運算,我們需要內建的 decimal 模組。概念做法是將二進制轉換成十進制數字後,再用 quantize 進行四捨五入,裡頭有個參數 rounding
可以設定強制四捨五入 ROUND_HALF_UP
:
from decimal import Decimal, ROUND_HALF_UP
print(Decimal(str(3.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)) #3.2
print(Decimal(str(3.16)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)) #3.2
print(Decimal(str(4.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)) #4.2
print(Decimal(str(4.16)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)) #4.2
print(Decimal(str(3.25)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)) #3.3
再來寫一版 round_v3 吧
比較值得注意的,是 quantize 在精確度是用 0.1, 0.01,… 表示,這部分比較不直覺,要多一步轉換(我笨笨地寫個 for loop 處理,對 format 不熟…)。最後記得,因為一開始有轉換成 decimal
,最後 return 前要再轉回來 float
資料格式上才不會錯誤,以利後續處理。
from decimal import Decimal, ROUND_HALF_UP
def round_v3(num, decimal):
str_deci = 1
for _ in range(decimal):
str_deci = str_deci / 10
str_deci = str(str_deci)
result = Decimal(str(num)).quantize(Decimal(str_deci), rounding=ROUND_HALF_UP)
result = float(result)
print(result)
return result
print(round_v3(3.15, 1))
print(round_v3(3.16, 1))
print(round_v3(4.15, 1))
print(round_v3(4.26, 1))
print(round_v3(3.25, 1))
# 輸出結果
3.2
3.2
4.2
4.3
3.3
這樣之後就用自己版本的 round_v3() 來做四捨五入就好啦~完成!
解決方法(已失效,感謝 Ling 提出)
2023.05.31 更新
因為電腦無法用二進制精準的儲存十進制的小數點,所以在 np.round() 的時候,電腦其實是採取 四捨、六入、五平分的概念:如果數字五的前一位數是偶數、無條件捨去;若前一位是奇數、無條件進位。
網路上有些解法是把 float 轉換成 decimal、再做四捨五入,但身為資料處理者,一定很常用 numpy 套件,剛好 numpy 也有 round函數 可以幫我們解決這個問題:
# 輸出結果:
3.2
3.2
4.2
4.2
好人做到底
我們也可以寫一個自己版本的 round 函數,但要注意的是原始的輸入值會是 float
,使用 numpy 所返回的值會是一個 np.float
格式的物件,我們要再把它轉回成 float
形式、以利後續資料分析及使用:
# 輸出
3.1
3.2