說說 Python3 中的數字處理
最近在處理訂單相關的問題,踩了數字的一些坑,在此記錄下。
其中有問題的程式碼涉及金額比較,便於描述,假設了下面一段程式碼
def is_paid(pay_price, paid_price): return pay_price == paid_price # 資料表中的記錄類似這樣 # id pay_price ... # 1 12.3 # ... # 操作如下 # 這裡使用了 SQLAlchemy 的 ORM 形式讀取資料 order = Order.query.filter_by(id=1).first() if is_paid(order.pay_price, 12.3): print('paid') else: print('unpaid') # 最後列印的卻是 unpaid
跟蹤程式碼才發現 order.pay_price 是 Decimal 型別,而 12.3 是浮點型別,Python 是強型別語言,型別不一樣當然不等。
>>> from decimal import Decimal as d >>> a = d('12.3') >>> b = 12.3 >>> type(a) <class 'decimal.Decimal'> >>> type(b) <class 'float'> >>> a Decimal('12.3') >>> b 12.3 >>> a == b False
仔細想想,有點不對,你看 1 == 1.0 就成立啊,不也是不同型別(整型和浮點型)嗎。
不管是不是強型別語言,數字之間作比較還是應該要能行的吧。
這裡沒有深挖,感覺就是 Python 設計的緣故吧。
所以,這裡應該怎麼作等值比較,試了下 math.isclose(a, b) ,嗯,行得通。
本應該在這裡結束了,但,不小心玩了下
>>> m = 0.1 + 0.2
>>> n = 0.3
>>> m == n
False
納尼
然後列印了下值
>>> m
0.30000000000000004
>>> n
0.3
我還有什麼話可說呢,這下讓我對 Python 浮點數的處理產生了懷疑。
也讓自己對以前所寫的數字比較產生了懷疑,天哪,全是 Bug 。不過後面想通了,不存在的 :)
>>> x = 1.0
>>> y = Decimal('2')
>>> x + y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'
這下,搞得我以後都不知道怎麼處理數字了。程式中很多地方都沒判斷型別,呀,又是一堆隱患。(同樣,還是想多了...)
為了理清自己的思路,又試了下
>>> z = 3
>>> y + 3
Decimal('5')
原來,Decimal 和整型是能進行算術運算的。
後面自己冷靜了下,終於想通了。
程式中,我們用的很多庫,涉及小數的基本上都是用的 Decimal 型別,比如 SQLAchemy ORM 取出來的小數資料都是此型別。
Decimal 之間比較一般精度的數字都是沒問題的,並且程式中我們定義數字的初始值基本都是整型 0 ,和 Deciamal 運算沒有問題,所以上面的疑慮都煙消雲散了。
所以,在以後的數字處理中,自己儘量只使用整型和 Decimal 型別,來避免上面的隱形問題。
不過,要注意
>>> t1 = Decimal(0.123)
>>> t2 = Deciaml('0.123')
>>> t1 == t2
False
這是由於浮點數 0.123 在轉為 Decimal 的時候失去了精度
>>> t1
Decimal('0.1229999999999999982236431605997495353221893310546875')
>>> t2
Decimal('0.123')
因此,定義 Decimal 型別的時候,我們儘量使用字串來避免這個問題。
本文首發於公眾號「小小後端」,關注並回復「1024」有驚喜哦。