1. 程式人生 > >說說 Python3 中的數字處理

說說 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」有驚喜哦。