使用者:Xyy23330121/Python/decimal
decimal 是一種特殊的浮點數,它可以用於解決 float 把十進制小數轉換成二進制無限循環小數,再捨入時產生的誤差。在 Python 中,decimal 的內容由 decimal 模塊提供。
float 儲存數字的方式,簡單來講就是用二進制的方式儲存三部分內容:正負號、二進制數字m
、二進制數字n
。來表示形如 +m * 2**n
或 -m * 2**n
的內容。decimal 與之類似,但它表示的是形如 +m * 10**n
的內容。
由於科學計數法中、底數的作用, float 保留的有效位數,是二進制的有效位數。十進制的有限小數、不一定能由二進制的有限小數表示出來。此時,取二進制小數中、有限部分的內容,一定會產生誤差。而 Decimal 保留的有效位數是十進制的有效位數。這種方式使得計算機的數字計算與人的直覺相符。
這並不代表 Decimal 不會產生捨入誤差,只是捨入誤差以符合直覺的方式出現——也就能夠簡單的以符合直覺的方式解決。
Decimal 也被一些語言稱作是貨幣值,這是因為貨幣運算一般不包含除法、也不會有超出限制的位數,從而 Decimal 能精確地計算商品的價格等內容。
import decimal as d
print(d.Decimal(10)) #10
print(d.Decimal(0.1)) #0.1000000000000000055511151231257827021181583404541015625
print(d.Decimal('0.1')) #0.1
print(d.Decimal('1.1e-1')) #0.11
print(d.Decimal('-1e-1')) #-0.1
print(d.Decimal('-inf')) #-Infinity
print(d.Decimal('nan')) #NaN
print(d.Decimal('-nan114514'))#-NaN114514
Decimal 可以由以上「類型構造器」方式,從整數、浮點數和字符串中生成。生成時,推薦使用字符串或整數。
對於浮點數,它會連同誤差的部分也精確地表現——從而誤差並沒有減小。
也可以用元組來創建 Decimal,我們見以下方式:
import decimal as d
# 符号位 有效数位 指数
num_tuple = (0, (1, 2, 3), -1)
print(d.Decimal(num_tuple)) #12.3
用元組創建 Decimal 時,元組需要為一個三元組。
其中,第一個元素為符號位,其數值為 0
或 1
,分別代表正數或負數。上面例子中是正數。
第二個元素為包含所有有效數位的元組,十進制的每一位都要分開表示,上面例子中、該元組表示整數 123
。
第三個元素表示指數部分的整數,上面例子使用了 -1
,從而指數部分是 。
從而,以上元組表示的數字是:。
Decimal 支持一切數字的運算。包括:
- 四則運算、帶餘除法、絕對值
- 用 int() 、 float() 等類型構造器,轉換為 int 或 float。
Decimal 在數學運算時,不能和 float 混用。在比較運算時是可以的。
Decimal 之間在進行 Python 帶餘除法時,取餘數的符號和被除數符號相同。
這個行為與浮點數的帶餘除法或整數的帶餘除法是不同的,而與 math.fmod 函數的行為相同。
Decimal 對象支持許多方法,包括用於數學運算的對數函數和向整數四捨五入等。讀者可以閱讀相關文檔[1]來了解其內容。
Decimal 的具體運算,比如精度等,是由上下文對象decimal.Context
規定的。
decimal 的上下文對象包含以下內容:
|
名稱 | 注釋 | |
---|---|---|---|
|
prec | Decimal 在計算時的有效位數。 這個精度對用類型構造器創建 Decimal 時的精度沒有影響。 | |
使用 32 位運行 Python 時,此項為 1 ~ 425000000 的整數, 使用 64 位時,此項為 1 ~ 999999999999999999 的整數。 | |||
|
rounding | 決定運算需要捨入時,Decimal的捨入行為。具體可以為以下模式: | |
值 | 模式 | ||
decimal.ROUND_CEILING | 向上捨入 | ||
decimal.ROUND_FLOOR | 向下捨入 | ||
decimal.ROUND_DOWN | 向零捨入 | ||
decimal.ROUND_UP | 向無窮大捨入 | ||
decimal.ROUND_HALF_DOWN | 捨入到最接近的數字 如果有兩個數字同樣接近,則向零捨入 | ||
decimal.ROUND_HALF_UP | 捨入到最接近的數字 如果有兩個數字同樣接近,則向無窮大捨入 | ||
decimal.ROUND_HALF_EVEN | 捨入到最接近的數字 如果有兩個數字同樣接近,則向偶數捨入 | ||
decimal.ROUND_05UP | 如果捨入後最後一位 為 0 或 5,則向無窮 大捨入; 否則,向零捨入。 | ||
|
traps | 一個字典,具體見「異常處理方式」章節 | |
|
flags | 一個字典,具體見「錯誤提示」章節 | |
|
Emin | 創建 Decimal 時,指數部分的最小值 | |
使用 32 位運行 Python 時,此項為 -425000000 ~ 0 的整數, 使用 64 位時,此項為 -999999999999999999 ~ 0 的整數。 | |||
Emax | 創建 Decimal 時,指數部分的最大值 | ||
使用 32 位運行 Python 時,此項為 0 ~ 425000000 的整數, 使用 64 位時,此項為 0 ~ 999999999999999999 的整數。 | |||
|
clamp | 若設為 1 ,則當指數部分過大時,會減小其指數部分,並移除數字末尾的零。這使得指數部分的限制為是 Emin - prec + 1 ~ Emax - prec + 1 的整數。若設為 0 ,則不進行這類操作,此時指數部分限制為是Emin ~ Emax 的整數。
| |
|
capitals | 若設為 1 ,則在打印 1*10^1 時,打印為 1E+1 。若設為 0 ,則打印為 1e1 。
|
decimal 模塊有一套特殊的異常處理方式,這個異常處理方式是由上下文對象的 traps 屬性決定的。traps 為一個內容為 错误类型:是否报错
的字典。
當出現「可以認為出錯了」的操作時,decimal 模塊會查詢對應錯誤類型的設置,如果 traps[错误类型]
的值為 True
,則會引發錯誤實例以報錯。
反之,如果 traps[错误类型]
的值為 False
,則不進行報錯。此時,操作的結果會被特殊處理。
以下是幾種異常類型,以及不報錯時的處理方法。
當指數超出當前上下文對象給出的上限時出現。
不報錯、且上下文中 clamp 為 1 時,會對指數進行修改,並向有效數字的末尾添加零,使實例正確反應數值。這對有效數字的位數有影響。
當指數超出當前上下文對象給出的下限時出現。
未報錯時,會對指數進行修改,並減少有效數字部分末尾的零,使實例正確反應數值。這對有效數字的位數有影響。
當數值超出當前上下文對象給出的限制中、可表示的最大數值時出現。
不報錯時,會進行捨入。視捨入方向,可能有兩個結果:
- 無窮大
- 可表示的最大數值
當數值超出當前上下文對象給出的限制中、可表示的最小數值時出現。
不報錯時,會進行捨入,並捨入為 0。
當被除數為有限數字,而除數為零時出現。有限數字指的是無窮大或 NaN
不報錯時,會讓計算結果變為無窮大。此時,當被除數與除數符號相同時,返回正無窮大;當被除數與除數符號不同時,返回負無窮大。
當產生無意義運算時出現。無意義運算可能為:
- 無窮大乘無窮大
- 無窮大乘零
- 無窮大除無窮大
- 以零為除數,作帶餘除法
- 被除數為無窮大,除數為有限數字,作帶餘除法
- 對負數求平方根
- 零的零次冪
- 有限數字的非整數次冪,或無窮大次冪
不報錯時,計算結果為 NaN。
當發生捨入時出現,不一定產生捨入誤差。
不報錯時,會進行捨入。
當發生捨入、且會產生捨入誤差時出現。
不報錯時,會進行捨入,並產生捨入誤差。
當 float 和 Decimal 混合使用時出現。
不報錯時,以下的計算可以進行:
- 類型構造器輸入浮點數
decimal.Decimal(float)
- 不等於的比較運算
decimal.Decimal < float
等
如果此項設置為報錯,又需要從 float 創建 Decimal 時,可以使用 classmethod decimal.Decimal.from_float(f)
。
這是以上所有錯誤的基類。它在默認情況下不在 flags 字典中,也不在 traps 字典中。
以下內容總結了各個信號的層級結構:
exceptions.ArithmeticError(exceptions.Exception)
DecimalException
Clamped
DivisionByZero(DecimalException, exceptions.ZeroDivisionError)
Inexact
Overflow(Inexact, Rounded)
Underflow(Inexact, Rounded, Subnormal)
InvalidOperation
Rounded
Subnormal
FloatOperation(DecimalException, exceptions.TypeError)
獲取當前使用的上下文。通過這個方法,也可以設置當前上下文的內容。比如:
import decimal as d
D = d.Decimal
current_context = d.getcontext()
print(current_context)
print(D(1)/D(7))
current_context.prec = 7
print(D(1)/D(7))
輸出為:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
0.1428571428571428571428571429
0.1428571
即便設置了不進行報錯,有時也需要了解是否出現了問題。上下文對象的 flags 屬性就是用於對異常進行提示的。
flags 是一個內容為 错误类型:是否出现过
的字典。當異常產生(但不一定報錯)時,flags[错误类型]
的值會被改為 True
。
我們看以下示例:
import decimal as d
D = d.Decimal
#解除 decimal.DivisionByZero 的报错,使程序不会因报错而终止。
current_context = d.getcontext()
current_context.traps[d.DivisionByZero] = 0
print("引发信号前:")
print(current_context)
print(current_context.flags[d.DivisionByZero])
#引发 decimal.DivisionByZero 信号。
D(1)/D(0)
#查看引发信号后的结果
print("引发信号后:")
print(current_context)
print(current_context.flags[d.DivisionByZero])
#清除之前的提示
current_context.clear_flags()
#查看清除提示后的结果
print("清除提示后:")
print(current_context)
print(current_context.flags[d.DivisionByZero])
輸出為:
引发信号前:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, Overflow])
False
引发信号后:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[DivisionByZero], traps=[InvalidOperation, Overflow])
True
清除提示后:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, Overflow])
False
對於已有的上下文對象,可以用這個方法,將其設置為當前上下文: decimal.setcontext(c)
decimal 模塊提供了幾種預設的上下文:
- decimal.DefaultContext 導入模塊時自動使用的、默認上下文。
- decimal.BasicContext 通用十進制算術規範描述所定義的標準上下文。
精度為 9,捨入為 ROUND_HALF_UP,不會進行任何錯誤提示,但所有錯誤都會報錯。 - decimal.ExtendedContext 通用十進制算術規範描述所定義的標準上下文。
精度為 9,捨入為 ROUND_HALF_UP,不會進行任何錯誤提示,且所有錯誤都不會報錯。
讀者也可以自行創建上下文對象。
decimal.Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None)
各個參數的內容如上面所示。特別的,默認值為 decimal.DefaultContext
的值,即:
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
在上述幾個示例中,讀者可能注意到:在打印時,traps 和 flags 並沒有顯示為字典。事實上,在創建上下文對象時,輸入的 traps 和 flags 也不需要必須是字典。這是因為在創建上下文對象時,decimal 模塊使用了以下代碼來將列錶轉化為字典:
_signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded,
Underflow, InvalidOperation, Subnormal, FloatOperation]
dc = DefaultContext
if traps is None:
self.traps = dc.traps.copy()
elif not isinstance(traps, dict):
self.traps = dict((s, int(s in traps)) for s in _signals + traps)
else:
self.traps = traps
if flags is None:
self.flags = dict.fromkeys(_signals, 0)
elif not isinstance(flags, dict):
self.flags = dict((s, int(s in flags)) for s in _signals + flags)
else:
self.flags = flags
通過閱讀以上代碼,如果讀者需要設置自己的、使用 Decimal 的運算。讀者應該了解如何向上下文對象添加運算中的信號。
decimal 模塊提供了一個上下文管理器 decimal.localcontext(ctx=None, **kwargs),它是這樣使用的:
def sin(x):
with localcontext() as ctx:
ctx.prec += 2 #增加计算精度
i, lasts, s, fact, num, sign = 0, 0, 1, 1, 1, 1
while s != lasts:
lasts = s
i += 2
fact *= i * (i-1)
num *= x * x
sign *= -1
s += num / fact * sign
return +s #进行一次“正”运算,重新舍入到当前精度
在創建此管理器時,此管理器會保存當前上下文,並提供當前上下文的副本作為新的當前上下文。在 with 子句中對當前上下文的修改,都不會影響已保存的上下文。當 with 語句結束時,此管理器會把保存的上下文重新設置為當前上下文。
這使得我們可以簡單地在局部增加計算精度。
特別的,如果輸入了 kwargs
,則管理器提供副本時,會對副本按照 kwargs
給出的屬性進行修改。
kwargs
提供了上下文對象所不支持的屬性時,會引發 TypeError。如果 kwargs
提供了無效的屬性值,則會引發 TypeError 或 ValueError。
Python 文檔中還提供了許多 decimal 的使用示例。包括正弦、餘弦等。
結合之前已學過的知識,讀者可以自行撰寫一個模塊來實現 decimal 的數學運算,或者使用 decimal 的特性來實現更多的功能。