1. 程式人生 > 其它 >python 浮點數相加相相除_來聊聊Python裡的“數”

python 浮點數相加相相除_來聊聊Python裡的“數”

技術標籤:python 浮點數相加相相除

Python中,數,用各種形式表示,不同形式的數有各自的用途。

整數

整數,令人驚歎於它的簡單。兩個整數相除,例如4/3,得到一個浮點數,並且(4/3)*3的結果也是浮點數4.0。即便你沒有定義浮點數,在進行除法運算的時候,它會自動出現。

浮點數

浮點數不是一般意義的數。按照數學上的規定,數應該遵循如下原則:減法是加法的逆運算,加法結合律,等等。

例如:

>>> 1 + 2 - 2 - 10>>> 0.1 + 0.2 - 0.2 - 0.12.7755575615628914e-17

兩個數相加,再分別減去它們,上述居然出現了不同的結果。

它們也不會遵循結合律:a + (b + c) = (a + b) + c

>>> a = 2**-53>>> (a + a) + 1 == a + (a + 1)False

以上僅僅是浮點數運算中存在的兩個“小問題”,還不令你驚訝嗎?此處不便將浮點數各種出乎意料的運算一一展現。

分數

很多看似簡單的程式,遇到分數,就會出問題,比如運算時間暴增,演算法的複雜度加倍。遇到分數的時候,演算法時間不是跟輸入成正比,而是指數增長。

如果時間足夠長,記憶體爆掉也是常見的。

加法就是其中一個典型例子

>>> print(set(type(p) for p in primes))>>> one = fractions.Fraction(1)>>> before = datetime.now()>>> res = sum(one/p for p in primes[:10000])>>> after = datetime.now()>>> print("It took", after-before)>>> print("Size of output", len(str(res)))>>> print("Approximate value", float(res)){}It took 0:01:16.033260Size of output 90676Approximate value 2.7092582487972945

這段程式,計算了一些素數的倒數的和。在膝上型電腦上,10000個這樣的數相加,要1分鐘,最終輸出結果的大小超過了90K。

對比著,執行浮點數運算,效能更好。

>>> print(set(type(p) for p in primes))>>> before = datetime.now()>>> res = sum(1/p for p in primes[:10000])>>> after = datetime.now()>>> print("It took", after-before)>>> print("Size of output", len(str(res)))>>> print("Approximate value", float(res)){}It took 0:00:00.000480Size of output 17Approximate value 2.709258248797317

這次執行時間小於1毫秒,並且,者還可能是因為用datetime測量產生的誤差,快了10000倍。而且輸出結果的大小僅有17位元,下降了1000多倍。然而,計算結果有誤差。

Approximate value 2.7092582487972945Approximate value 2.709258248797317                    1234567891234

誤差低於 10的-14次方,這就如同將火箭發射月球上偏差了1毫米,用浮點數計算得到的結果足夠精確,並且效率更高。

對此,一般的觀點是:Python進行分數運算很慢。對此,Python可以承擔10倍的責任,但不是10000倍。有一個第三方模組,quicktions,用Cython執行分數的運算。

用quicktions,真的“很快”。在我的膝上型電腦上,上面那個程式的時間,從1分16秒,縮短到1分15秒。

問題在於程式本身,在程式中,我精心選擇了一種輸入方案,以素數作為分母進行分數相加,這本來就是一種很壞的情況。

小數

小數在財務中用途最廣,最無聊的是居然以法律的方式規定了小數的形式。然而,Python中所有的小數點運算,都有上下文精確度問題,對此,可以用專門的模組解決。

>>> getcontext().prec = 6>>> Decimal(1) / Decimal(7)Decimal('0.142857')>>> getcontext().prec = 28>>> Decimal(1) / Decimal(7)Decimal('0.1428571428571428571428571429')

在實際專案中,程式碼中設定精度的位置和進行計算的位置可能間隔幾百行,計算可以在一個函式中,也可以在另外一個檔案。

最安全的方法是使用localcontext:

>>> getcontext().prec = 6>>> # 6853 lines elided... with localcontext() as ctx:...     ctx.prec = 10...     Decimal(1) / Decimal(7)...Decimal('0.1428571429')

只要你認真地用localcontext,小數運算不會出問題。

總結

你在程式中用到數字的時候,是否想過:應該用什麼型別?會發生什麼?誤差重要嗎?

什麼也不想,會意味著暗藏bug。

21e668b7a440ae0db932eeb27fae5a44.png