Python自帶的decimal模塊用於十進制數學計算,它是在浮點類型的基礎上設計的,可以非常精確地在計算機中存儲和計算,精度優於floating point,因爲浮點數並不能精確的表示十進制數,因爲計算機由底層CPU和IEEE 754標準通過自己的浮點單位去執行算術時的特徵,因此對於精度要求高但效率不要求的場景,比如財務等,decimal可以較好的替換float類型。
Decimal重載了簡單的算術運算符,所以可以採用內置數值類型同樣的方式處理 Decimal實例。Decimal構造函數取一個整數或字符串作爲參數。使用浮點數創建 Decimal 之前,可以先將浮點數轉換爲一個字符串,使調用者能夠顯式地處理值得位數,還可以由元組創建,其中包含一個符號標誌(0 表示正,1 表示負)、數字 tuple 以及一個整數指數。
>>>
>>> f1 = 1.23
>>> f2 = 3.21
>>> f1 + f2
4.4399999999999995
>>>
>>> from decimal import Decimal
>>> from decimal import getcontext
>>>
>>> d1 = Decimal('1.23')
>>> d2 = Decimal('3.21')
>>> d3 = d1 + d2
>>> print(type(d3), d3)
<class 'decimal.Decimal'> 4.44
>>> # 很準,位數也不變
>>>
>>>
>>> t1 = (1, (1, 1), -2)
>>> Decimal(t1)
Decimal('-0.11')
>>> t2 = (1, (1, 2), 3)
>>> Decimal(t2)
Decimal('-1.2E+4')
>>> t3 = (0, (1, 2), 3)
>>> Decimal(t3)
Decimal('1.2E+4')
>>>
>>> str(f3)
'2.468'
>>>
但相除或相乘的話,小數點的位數還是變了:
>>>
>>> d4 = d2 / d1
>>> d4
Decimal('2.609756097560975609756097561')
>>> d3
Decimal('4.44')
>>> d5 = d1 * d2
>>> d5
Decimal('3.9483')
>>>
可以通過getcontext().prec = x(x爲你想要的精度來設置)來設置Decimal類型保留有效位數,注意不是保留小數的位數,如果要保留小數位數用round()。
>>>
>>> context = getcontext()
>>> context.prec = 3 # 保留3位數
>>> print(context)
Context(prec=3, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
>>> d6 = d1 * d2
>>> d6
Decimal('3.95')
>>> d7 = d2 / d1
>>> d7
Decimal('2.61')
>>> d8 = d1 + d2
>>> d8
Decimal('4.44')
>>>
由上可以看到,設置一次getcontext().prec = x後,其下面的Decimal類型全部保留了x位數,有時不需要它管轄得太廣,那麼我們需要用localcontext創建一個局部上下文。
>>> from decimal import localcontext
>>>
>>> with localcontext() as ctx:
ctx.prec = 4
d9 = d2 / d1
d10 = d1 + d2
print(d9, d10)
2.610 4.44
>>> with localcontext() as ctx:
ctx.prec = 5
d11 = d2 / d1
d12 = d1 * d2
print(d11, d12)
2.6098 3.9483
>>>
>>> from decimal import Decimal
>>>
>>> v = Decimal(str(1.536842))
>>> v1 = Decimal(str(3.0125873))
>>> v
Decimal('1.536842')
>>> v1
Decimal('3.0125873')
>>>
>>> from decimal import localcontext
>>> with localcontext() as ctx:
ctx.prec = 5 # 保留5位有效數字
v2 = v + v1
v3 = v * v1
print(v2, v3)
4.5494 4.6299
>>>
>>> # 保留5位小數
>>> v4 = round(float(v + v1), 5)
>>> v4
4.54943
>>> v5 = round(float(v * v1), 5)
>>> v5
4.62987
>>>
如果覺得局部上下文設置精度太不靈活,要用的時候都得設置一次,那麼還可以使用實例上下文。
>>>
>>> ctx1 = getcontext().copy() # 創建一個帶精度的context
>>> ctx1.prec = 2 # 設置context精度
>>> ctx2 = getcontext().copy() # 創建一個帶精度的context
>>> ctx2.prec = 5 # 設置context精度
>>>
>>> d13 = ctx1.create_decimal(str(f1))
>>> d13
Decimal('1.2')
>>> d14 = ctx1.create_decimal(str(f2))
>>> d14
Decimal('3.2')
>>> d13 + d14
Decimal('4.4')
>>> d13 * d14
Decimal('3.84')
>>> d14 / d13
Decimal('2.67')
>>> # 從上面看到,create_decimal創建Decimal實例時精度是對的,但是他們的實例進行算術運算後,精度就變了
>>> d15 = ctx2.create_decimal(str(f1))
>>> d15
Decimal('1.23')
>>> d16 = ctx2.create_decimal(str(f2))
>>> d16
Decimal('3.21')
>>> d15 + d16
Decimal('4.44')
>>> d15 * d16
Decimal('3.95')
>>> d16 / d15
Decimal('2.61')
>>>
>>> f3 = 2.468
>>> ctx3 = getcontext().copy() # 創建一個帶精度的context
>>> ctx3.prec = 5 # 設置context精度
>>>
>>> d17 = ctx3.create_decimal(str(f3))
>>> d17
Decimal('2.468')
>>> d17 + d15
Decimal('3.70')
>>> d17 * d15
Decimal('3.04')
>>> d17 / d15
Decimal('2.01')
>>>
rounding取整,有多種選擇,以保證值在所需精度範圍內。
•ROUND_CEILING 總是趨向於無窮大向上取整。
•ROUND_DOWN 總是趨向 0 取整。
•ROUND_FLOOR 總是趨向負無窮大向下取整。
•ROUND_HALF_DOWN 如果最後一個有效數字大於或等於 5 則朝 0 反方向取整;否則,趨向 0 取整。
•ROUND_HALF_EVEN 類似於 ROUND_HALF_DOWN,不過,如果最後一個有效數字值爲 5,則會檢查前一位。偶數值會導致結果向下取整,奇數值導致結果向上取整。
•ROUND_HALF_UP 類似於 ROUND_HALF_DOWN,不過如果最後一位有效數字爲 5,值會朝 0 的反方向取整。
•ROUND_UP 朝 0 的反方向取整。
•ROUND_05UP 如果最後一位是 0 或 5,則朝 0 的反方向取整;否則向 0 取整。
>>>
>>> import decimal
>>>
>>> context = decimal.getcontext()
>>> ROUNDING_MODES = [
'ROUND_CEILING',
'ROUND_DOWN',
'ROUND_FLOOR',
'ROUND_HALF_DOWN',
'ROUND_HALF_EVEN',
'ROUND_HALF_UP',
'ROUND_UP',
'ROUND_05UP',
]
>>> context.prec = 3
>>> for mode in ROUNDING_MODES:
context.rounding = getattr(decimal, mode)
value = decimal.Decimal(str(f3))
print(value)
2.468
2.468
2.468
2.468
2.468
2.468
2.468
2.468
>>>
>>> context.prec = 3
>>> for mode in ROUNDING_MODES:
context.rounding = getattr(decimal, mode)
value = decimal.Decimal(str(f3)) / decimal.Decimal('1.25689')
print(value)
1.97
1.96
1.96
1.96
1.96
1.96
1.97
1.96
除了期望的數字值,Decimal 還可以表示很多特殊值,包括正負無窮大值、“不是一個數”(NaN)和 0,python並沒有特殊的語法來表示這些特殊的浮點值,但是可以使用float()來創建它們。
>>> import decimal
>>>
>>> for value in ['Infinity', 'inf', 'NaN', 'nan', '0']:
print (decimal.Decimal(value), decimal.Decimal('-' + value))
Infinity -Infinity
Infinity -Infinity
NaN -NaN
NaN -NaN
0 -0
與無窮大值相加會返回另一個無窮大值。與 NaN 比較相等性總會返回 false,而比較不等性總會返回 true。與 NaN 比較大小來確定排序順序沒有明確定義,這會導致一個錯誤。
>>>
>>> decimal.Decimal('inf') + 1
Decimal('Infinity')
>>> decimal.Decimal('-inf') + 100
Decimal('-Infinity')
>>> decimal.Decimal('nan') == decimal.Decimal('inf')
False
>>> decimal.Decimal('nan') == decimal.Decimal('-inf')
False
>>> decimal.Decimal('nan') != decimal.Decimal(0)
True
>>>