decimal --- 十進位固定點和浮點運算¶
原始碼:Lib/decimal.py
decimal 模組提供對快速且正確捨入的十進位浮點運算的支援。它提供了優於 float 資料型別的幾項優勢:
Decimal 「是基於一個浮點模型,該模型在設計時考量了人類的需求,並且必然有一個最重要的指導原則——電腦必須提供一種與人們在學校學到的算術相同運作方式的算術。」——摘自十進位算術規格。
Decimal 數字可以被精確表示。相對地,像
1.1和2.2這樣的數字在二進位浮點數中沒有精確的表示方式。終端使用者通常不會期望1.1 + 2.2顯示為3.3000000000000003,但這正是二進位浮點數的表現。精確性延續到算術運算中。在十進位浮點數中,
0.1 + 0.1 + 0.1 - 0.3完全等於零。在二進位浮點數中,結果是5.5511151231257827e-017。雖然接近零,但這些差異會妨礙可靠的相等性測試,並且差異可能累積。因此,在具有嚴格相等不變量的會計應用程式中,decimal 是首選。decimal 模組包含了有效位數的概念,因此
1.30 + 1.20等於2.50。尾隨零被保留以表示有效性。這是金融應用程式的慣用呈現方式。對於乘法,「教科書」方法使用被乘數中的所有數字。例如,1.3 * 1.2得到1.56,而1.30 * 1.20得到1.5600。與基於硬體的二進位浮點數不同,decimal 模組具有使用者可變的精度(預設為 28 位),可以根據給定問題的需要設定得足夠大:
>>> from decimal import * >>> getcontext().prec = 6 >>> Decimal(1) / Decimal(7) Decimal('0.142857') >>> getcontext().prec = 28 >>> Decimal(1) / Decimal(7) Decimal('0.1428571428571428571428571429')
二進位和十進位浮點數都是根據已發布的標準實作的。雖然內建的 float 型別只暴露其功能的一小部分,但 decimal 模組暴露了標準的所有必要部分。必要時,程式設計師對捨入和訊號處理有完全的控制權。這包括透過使用例外來阻止任何不精確運算以強制執行精確算術的選項。
decimal 模組被設計為支援「不帶偏見地同時支援精確無捨入的十進位算術(有時稱為固定點算術)和有捨入的浮點算術。」——摘自十進位算術規格。
模組設計圍繞三個概念:decimal 數字、算術情境和訊號。
decimal 數字是不可變的。它有符號、係數數字和指數。為了保持有效性,係數數字不會截斷尾隨零。Decimal 也包含特殊值,如 Infinity、-Infinity 和 NaN。標準也區分 -0 和 +0。
算術情境是一個指定精度、捨入規則、指數限制、指示運算結果的旗標,以及決定訊號是否被視為例外的陷阱啟用器的環境。捨入選項包括 ROUND_CEILING、ROUND_DOWN、ROUND_FLOOR、ROUND_HALF_DOWN、ROUND_HALF_EVEN、ROUND_HALF_UP、ROUND_UP 和 ROUND_05UP。
訊號是在計算過程中產生的例外條件群組。根據應用程式的需求,訊號可能被忽略、視為資訊性質,或被視為例外。decimal 模組中的訊號包括:Clamped、InvalidOperation、DivisionByZero、Inexact、Rounded、Subnormal、Overflow、Underflow 和 FloatOperation。
每個訊號都有一個旗標和一個陷阱啟用器。當遇到訊號時,其旗標被設定為一,然後,如果陷阱啟用器被設定為一,就會引發例外。旗標是黏性的,因此使用者需要在監控計算之前重設它們。
也參考
IBM 的一般十進位算術規範,一般十進位算術規範。
快速入門教學¶
使用 decimal 的通常起始步驟是引入模組、使用 getcontext() 檢視目前的情境,以及若有必要的話,設定精度、捨入方式或啟用陷阱的新值:
>>> from decimal import *
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
capitals=1, clamp=0, flags=[], traps=[Overflow, DivisionByZero,
InvalidOperation])
>>> getcontext().prec = 7 # 設定新的精度
Decimal 實例可以從整數、字串、浮點數或 tuple 建構。從整數或浮點數建構會對該整數或浮點數的值進行精確轉換。Decimal 數字包括特殊值,例如代表「非數字」的 NaN、正負 Infinity 和 -0::
>>> getcontext().prec = 28
>>> Decimal(10)
Decimal('10')
>>> Decimal('3.14')
Decimal('3.14')
>>> Decimal(3.14)
Decimal('3.140000000000000124344978758017532527446746826171875')
>>> Decimal((0, (3, 1, 4), -2))
Decimal('3.14')
>>> Decimal(str(2.0 ** 0.5))
Decimal('1.4142135623730951')
>>> Decimal(2) ** Decimal('0.5')
Decimal('1.414213562373095048801688724')
>>> Decimal('NaN')
Decimal('NaN')
>>> Decimal('-Infinity')
Decimal('-Infinity')
如果 FloatOperation 訊號被捕捉,在建構函式或排序比較中意外混用 decimal 和 float 會引發例外:
>>> c = getcontext()
>>> c.traps[FloatOperation] = True
>>> Decimal(3.14)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.FloatOperation: [<class 'decimal.FloatOperation'>]
>>> Decimal('3.5') < 3.7
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.FloatOperation: [<class 'decimal.FloatOperation'>]
>>> Decimal('3.5') == 3.5
True
在 3.3 版被加入.
新 Decimal 的精度僅由輸入的數字位數決定。情境精度和捨入只會在算術運算期間發揮作用。
>>> getcontext().prec = 6
>>> Decimal('3.0')
Decimal('3.0')
>>> Decimal('3.1415926535')
Decimal('3.1415926535')
>>> Decimal('3.1415926535') + Decimal('2.7182818285')
Decimal('5.85987')
>>> getcontext().rounding = ROUND_UP
>>> Decimal('3.1415926535') + Decimal('2.7182818285')
Decimal('5.85988')
如果超過 C 版本的內部限制,建構 decimal 會引發 InvalidOperation:
>>> Decimal("1e9999999999999999999")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]
在 3.3 版的變更.
Decimal 與 Python 的其他部分互動良好。以下是一個小小的 decimal 浮點數展示:
>>> data = list(map(Decimal, '1.34 1.87 3.45 2.35 1.00 0.03 9.25'.split()))
>>> max(data)
Decimal('9.25')
>>> min(data)
Decimal('0.03')
>>> sorted(data)
[Decimal('0.03'), Decimal('1.00'), Decimal('1.34'), Decimal('1.87'),
Decimal('2.35'), Decimal('3.45'), Decimal('9.25')]
>>> sum(data)
Decimal('19.29')
>>> a,b,c = data[:3]
>>> str(a)
'1.34'
>>> float(a)
1.34
>>> round(a, 1)
Decimal('1.3')
>>> int(a)
1
>>> a * 5
Decimal('6.70')
>>> a * b
Decimal('2.5058')
>>> c % a
Decimal('0.77')
Decimal 可以(透過 format() 內建函式或 f-string(f 字串))使用與內建 float 型別相同的格式化語法(見 格式規格 (Format Specification) 迷你語言)以固定點或科學記號格式化:
>>> format(Decimal('2.675'), "f")
'2.675'
>>> format(Decimal('2.675'), ".2f")
'2.68'
>>> f"{Decimal('2.675'):.2f}"
'2.68'
>>> format(Decimal('2.675'), ".2e")
'2.68e+0'
>>> with localcontext() as ctx:
... ctx.rounding = ROUND_DOWN
... print(format(Decimal('2.675'), ".2f"))
...
2.67
而且 Decimal 也提供一些數學函式:
>>> getcontext().prec = 28
>>> Decimal(2).sqrt()
Decimal('1.414213562373095048801688724')
>>> Decimal(1).exp()
Decimal('2.718281828459045235360287471')
>>> Decimal('10').ln()
Decimal('2.302585092994045684017991455')
>>> Decimal('10').log10()
Decimal('1')
quantize() 方法將數字捨入到固定的指數。此方法對於經常將結果捨入到固定位數的金融應用程式很有用:
>>> Decimal('7.325').quantize(Decimal('.01'), rounding=ROUND_DOWN)
Decimal('7.32')
>>> Decimal('7.325').quantize(Decimal('1.'), rounding=ROUND_UP)
Decimal('8')
如上所示,getcontext() 函式存取目前的情境並允許變更設定。這種方法滿足大多數應用程式的需求。
對於更進階的工作,使用 Context() 建構函式來建立替代的 context 可能會很有用。要啟用替代的 context,使用 setcontext() 函式。
根據標準,decimal 模組提供了兩個可直接使用的標準 context,BasicContext 和 ExtendedContext。前者對除錯特別有用,因為許多陷阱都被啟用了:
>>> myothercontext = Context(prec=60, rounding=ROUND_HALF_DOWN)
>>> setcontext(myothercontext)
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857142857142857142857142857')
>>> ExtendedContext
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
capitals=1, clamp=0, flags=[], traps=[])
>>> setcontext(ExtendedContext)
>>> Decimal(1) / Decimal(7)
Decimal('0.142857143')
>>> Decimal(42) / Decimal(0)
Decimal('Infinity')
>>> setcontext(BasicContext)
>>> Decimal(42) / Decimal(0)
Traceback (most recent call last):
File "<pyshell#143>", line 1, in -toplevel-
Decimal(42) / Decimal(0)
DivisionByZero: x / 0
情境也有訊號旗標用於監控計算過程中遇到的例外條件。旗標會保持設定狀態直到明確清除,因此最好在每組受監控的計算之前使用 clear_flags() 方法清除旗標:
>>> setcontext(ExtendedContext)
>>> getcontext().clear_flags()
>>> Decimal(355) / Decimal(113)
Decimal('3.14159292')
>>> getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
flags 條目顯示對 pi 的有理逼近已被捨入(超出情境精度的數字被丟棄),並且結果是不精確的(一些被丟棄的數字非零)。
個別陷阱使用情境的 traps 屬性中的字典設定:
>>> setcontext(ExtendedContext)
>>> Decimal(1) / Decimal(0)
Decimal('Infinity')
>>> getcontext().traps[DivisionByZero] = 1
>>> Decimal(1) / Decimal(0)
Traceback (most recent call last):
File "<pyshell#112>", line 1, in -toplevel-
Decimal(1) / Decimal(0)
DivisionByZero: x / 0
大多數程式只在程式開始時調整一次目前情境。在許多應用程式中,資料在迴圈內透過單次轉換轉換為 Decimal。設定情境並建立 decimal 後,程式的大部分操作資料的方式與其他 Python 數值型別沒有什麼不同。
Decimal 物件¶
- class decimal.Decimal(value='0', context=None)¶
基於 value 建構一個新的
Decimal物件。value 可以是整數、字串、元組、
float或其他Decimal物件。如果沒有提供 value,則回傳Decimal('0')。如果 value 是字串,在移除前後空白字元以及整個字串中的底線後,它應該符合十進位數字字串語法:sign ::= '+' | '-' digit ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' indicator ::= 'e' | 'E' digits ::= digit [digit]... decimal-part ::= digits '.' [digits] | ['.'] digits exponent-part ::= indicator [sign] digits infinity ::= 'Infinity' | 'Inf' nan ::= 'NaN' [digits] | 'sNaN' [digits] numeric-value ::= decimal-part [exponent-part] | infinity numeric-string ::= [sign] numeric-value | [sign] nan
在上面出現
digit的地方也允許其他 Unicode 十進位數字。這些包括來自各種其他字母表的十進位數字(例如阿拉伯-印度數字和天城數字)以及全形數字'\uff10'到'\uff19'。大小寫不重要,所以例如inf、Inf、INFINITY和iNfINity都是正無窮大的可接受拼寫。如果 value 是
tuple,它應該有三個元件:一個符號(0代表正數或1代表負數)、一個數字的tuple,以及一個整數指數。例如,Decimal((0, (1, 4, 1, 4), -3))回傳Decimal('1.414')。如果 value 是
float,二進位浮點數值會被無損轉換為其精確的十進位等價值。這種轉換通常需要 53 位或更多位數的精度。例如,Decimal(float('1.1'))轉換為Decimal('1.100000000000000088817841970012523233890533447265625')。context 精度不會影響儲存多少位數。這完全由 value 中的位數決定。例如,即使情境精度只有三位,
Decimal('3.00000')也會記錄所有五個零。context 引數的目的是決定當 value 是格式不正確的字串時該怎麼辦。如果情境捕捉
InvalidOperation,就會引發例外;否則,建構函式會回傳一個值為NaN的新 Decimal。一旦建構,
Decimal物件就是不可變的。在 3.2 版的變更: 建構函式的引數現在允許是
float實例。在 3.3 版的變更: 如果設定了
FloatOperation陷阱,float引數會引發例外。預設情況下陷阱是關閉的。在 3.6 版的變更: 允許使用底線進行分組,就像程式碼中的整數和浮點數字面值一樣。
Decimal 浮點數物件與其他內建數值型別(如
float和int)共享許多屬性。所有常用的數學運算和特殊方法都適用。同樣地,decimal 物件可以被複製、pickle 序列化、列印、用作字典鍵、用作集合元素、比較、排序,以及強制轉換為其他型別(如float或int)。Decimal 物件的算術運算與整數和浮點數的算術運算之間有一些小差異。當餘數運算子
%應用於 Decimal 物件時,結果的符號是 被除數 的符號,而不是除數的符號:>>> (-7) % 4 1 >>> Decimal(-7) % Decimal(4) Decimal('-3')
整數除法運算子
//的行為類似,回傳真商的整數部分(向零截斷)而不是其底數,以保持通常的恆等式x == (x // y) * y + x % y:>>> -7 // 4 -2 >>> Decimal(-7) // Decimal(4) Decimal('-1')
%和//運算子分別實作規格中描述的remainder和divide-integer運算。Decimal 物件通常不能在算術運算中與浮點數或
fractions.Fraction實例結合:例如,嘗試將Decimal與float相加會引發TypeError。但是,可以使用 Python 的比較運算子來比較Decimal實例x與另一個數字y。這避免了在不同型別的數字之間進行相等比較時產生令人困惑的結果。在 3.2 版的變更:
Decimal實例與其他數值型別之間的混合型別比較現在完全支援。除了標準數值屬性外,decimal 浮點數物件也有許多專門的方法:
- adjusted()¶
在移出係數的最右邊數字直到只剩下前導數字後,回傳調整後的指數:
Decimal('321e+5').adjusted()回傳七。用於確定最高有效數字相對於小數點的位置。
- as_integer_ratio()¶
回傳一對表示給定
Decimal實例作為分數的整數(n, d),以最簡形式且分母為正:>>> Decimal('-3.14').as_integer_ratio() (-157, 50)
轉換是精確的。對無窮大引發 OverflowError,對 NaN 引發 ValueError。
在 3.6 版被加入.
- as_tuple()¶
回傳數字的 named tuple 表示形式:
DecimalTuple(sign, digits, exponent)。
- compare(other, context=None)¶
比較兩個 Decimal 實例的值。
compare()回傳一個 Decimal 實例,如果任一運算元是 NaN,則結果是 NaN:a or b is a NaN ==> Decimal('NaN') a < b ==> Decimal('-1') a == b ==> Decimal('0') a > b ==> Decimal('1')
- compare_signal(other, context=None)¶
此運算與
compare()方法相同,除了所有 NaN 都會發出訊號。也就是說,如果兩個運算元都不是發出訊號的 NaN,那麼任何安靜的 NaN 運算元都會被視為發出訊號的 NaN。
- compare_total(other, context=None)¶
使用抽象表示而不是數值來比較兩個運算元。類似於
compare()方法,但結果對Decimal實例給出全序。在此排序中,具有相同數值但不同表示的兩個Decimal實例比較為不相等:>>> Decimal('12.0').compare_total(Decimal('12')) Decimal('-1')
安靜和發出訊號的 NaN 也包含在全序中。如果兩個運算元具有相同的表示,此函式的結果是
Decimal('0');如果第一個運算元在全序中低於第二個,則為Decimal('-1');如果第一個運算元在全序中高於第二個運算元,則為Decimal('1')。有關全序的詳細資訊,請參閱規範。此運算不受情境影響且是安靜的:不會變更旗標也不會執行捨入。例外情況下,如果第二個運算元無法精確轉換,C 版本可能會引發 InvalidOperation。
- compare_total_mag(other, context=None)¶
如
compare_total()般使用抽象表示而不是值來比較兩個運算元,但忽略每個運算元的符號。x.compare_total_mag(y)等於x.copy_abs().compare_total(y.copy_abs())。此運算不受情境影響且是安靜的:不會變更旗標也不會執行捨入。例外情況下,如果第二個運算元無法精確轉換,C 版本可能會引發 InvalidOperation。
- conjugate()¶
只是回傳 self,此方法僅為符合 Decimal 規範。
- copy_abs()¶
回傳引數的絕對值。此運算不受情境影響且是安靜的:不會變更旗標也不會執行捨入。
- copy_negate()¶
回傳引數的否定。此運算不受情境影響且是安靜的:不會變更旗標也不會執行捨入。
- copy_sign(other, context=None)¶
回傳第一個運算元的副本,其符號被設定為與第二個運算元的符號相同。例如:
>>> Decimal('2.3').copy_sign(Decimal('-1.5')) Decimal('-2.3')
此運算不受情境影響且是安靜的:不會變更旗標也不會執行捨入。例外情況下,如果第二個運算元無法精確轉換,C 版本可能會引發 InvalidOperation。
- exp(context=None)¶
回傳給定數字的(自然)指數函式
e**x的值。使用ROUND_HALF_EVEN捨入模式正確地捨入結果。>>> Decimal(1).exp() Decimal('2.718281828459045235360287471') >>> Decimal(321).exp() Decimal('2.561702493119680037517373933E+139')
- classmethod from_float(f, /)¶
-
注意
Decimal.from_float(0.1)與Decimal('0.1')不同。由於 0.1 在二進位浮點數中無法精確表示,該值被儲存為最接近的可表示值0x1.999999999999ap-4。該值在十進位中的等價值是0.1000000000000000055511151231257827021181583404541015625。>>> Decimal.from_float(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> Decimal.from_float(float('nan')) Decimal('NaN') >>> Decimal.from_float(float('inf')) Decimal('Infinity') >>> Decimal.from_float(float('-inf')) Decimal('-Infinity')
在 3.1 版被加入.
- classmethod from_number(number, /)¶
替代建構函式,僅接受
float、int或Decimal的實例,但不接受字串或 tuple。>>> Decimal.from_number(314) Decimal('314') >>> Decimal.from_number(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> Decimal.from_number(Decimal('3.14')) Decimal('3.14')
在 3.14 版被加入.
- fma(other, third, context=None)¶
融合乘加。回傳 self*other+third,對中間產品 self*other 不進行捨入。
>>> Decimal(2).fma(3, 5) Decimal('11')
- ln(context=None)¶
回傳運算元的自然(以 e 為底)對數。使用
ROUND_HALF_EVEN捨入模式正確地捨入結果。
- log10(context=None)¶
回傳運算元的十進位對數。使用
ROUND_HALF_EVEN捨入模式正確地捨入結果。
- logb(context=None)¶
對於非零數字,回傳其運算元的調整指數作為
Decimal實例。如果運算元是零,則回傳Decimal('-Infinity')並引發DivisionByZero旗標。如果運算元是無窮大,則回傳Decimal('Infinity')。
- logical_and(other, context=None)¶
logical_and()是一個邏輯運算,接受兩個 logical operands (參見 邏輯運算元)。結果是兩個運算元的逐位and。
- logical_invert(context=None)¶
logical_invert()是一個邏輯運算。結果是運算元的逐位反轉。
- logical_or(other, context=None)¶
logical_or()是一個邏輯運算,接受兩個 logical operands (參見 邏輯運算元)。結果是兩個運算元的逐位or。
- logical_xor(other, context=None)¶
logical_xor()是一個邏輯運算,接受兩個 logical operands (參見 邏輯運算元)。結果是兩個運算元的逐位互斥或。
- max(other, context=None)¶
類似於
max(self, other),但在回傳前會套用情境捨入規則,且NaN值會被發出訊號或忽略(取決於情境以及它們是發出訊號或安靜的)。
- min(other, context=None)¶
類似於
min(self, other),但在回傳前會套用情境捨入規則,且NaN值會被發出訊號或忽略(取決於情境以及它們是發出訊號或安靜的)。
- next_minus(context=None)¶
回傳在給定情境中(或如果沒有提供情境則在目前執行緒的情境中)可表示的最大數字,且該數字小於給定的運算元。
- next_plus(context=None)¶
回傳在給定情境中(或如果沒有提供情境則在目前執行緒的情境中)可表示的最小數字,且該數字大於給定的運算元。
- next_toward(other, context=None)¶
如果兩個運算元不相等,回傳在第二個運算元方向上最接近第一個運算元的數字。如果兩個運算元在數值上相等,回傳第一個運算元的副本,其符號設定為與第二個運算元的符號相同。
- normalize(context=None)¶
用於在目前情境或指定情境中產生等價類別的標準值。
這與一元加法運算的語意相同,但如果最終結果是有限的,則會被簡化為最簡形式,移除所有尾隨零並保留符號。也就是說,當係數非零且是十的倍數時,係數除以十,指數增加 1。否則(係數為零)指數設定為 0。在所有情況下符號都不變。
例如,
Decimal('32.100')和Decimal('0.321000e+2')都正規化為等價值Decimal('32.1')。注意捨入是在簡化為最簡形式 之前 套用的。
在規格的最新版本中,此運算也被稱為
reduce。
- number_class(context=None)¶
回傳描述運算元 類別 的字串。回傳值是下列十個字串之一。
"-Infinity",表示運算元是負無窮大。"-Normal",表示運算元是負的正規數。"-Subnormal",表示運算元是負的次正規數。"-Zero",表示運算元是負零。"+Zero",表示運算元是正零。"+Subnormal",表示運算元是正的次正規數。"+Normal",表示運算元是正的正規數。"+Infinity",表示運算元是正無窮大。"NaN",表示運算元是安靜型 NaN(Not a Number,非數字)。"sNaN",表示運算元是發訊型 NaN (signaling NaN)。
- quantize(exp, rounding=None, context=None)¶
回傳一個值,該值等於第一個運算元在捨入後並具有第二個運算元的指數。
>>> Decimal('1.41421356').quantize(Decimal('1.000')) Decimal('1.414')
與其他運算不同,如果 quantize 運算後係數的長度大於精度,則會發出
InvalidOperation訊號。這保證了除非有錯誤條件,否則量化指數總是等於右運算元的指數。同樣與其他運算不同,quantize 永遠不會發出 Underflow 訊號,即使結果是次正規且不精確的。
如果第二個運算元的指數大於第一個,則可能需要捨入。在這種情況下,捨入模式由
rounding引數決定(如果有提供),否則由給定的context引數決定;如果兩個引數都沒有提供,則使用目前執行緒情境的捨入模式。
- remainder_near(other, context=None)¶
回傳 self 除以 other 的餘數。這與
self % other不同之處在於餘數的符號被選擇以最小化其絕對值。更精確地說,回傳值是self - n * other,其中n是最接近self / other精確值的整數,如果兩個整數同樣接近,則選擇偶數。如果結果是零,則其符號將是 self 的符號。
>>> Decimal(18).remainder_near(Decimal(10)) Decimal('-2') >>> Decimal(25).remainder_near(Decimal(10)) Decimal('5') >>> Decimal(35).remainder_near(Decimal(10)) Decimal('-5')
- rotate(other, context=None)¶
回傳將第一個運算元的數字旋轉第二個運算元指定數量的結果。第二個運算元必須是在 -precision 到 precision 範圍內的整數。第二個運算元的絕對值給出要旋轉的位數。如果第二個運算元是正數,則向左旋轉;否則向右旋轉。第一個運算元的係數如有必要會在左側填補零以達到精度長度。第一個運算元的符號和指數不變。
- same_quantum(other, context=None)¶
測試 self 和 other 是否具有相同的指數,或兩者是否都是
NaN。此運算不受情境影響且是安靜的:不會變更旗標也不會執行捨入。例外情況下,如果第二個運算元無法精確轉換,C 版本可能會引發 InvalidOperation。
- scaleb(other, context=None)¶
回傳第一個運算元,其指數由第二個運算元調整。等價地,回傳第一個運算元乘以
10**other。第二個運算元必須是整數。
- shift(other, context=None)¶
回傳將第一個運算元的數字移位第二個運算元指定數量的結果。第二個運算元必須是在 -precision 到 precision 範圍內的整數。第二個運算元的絕對值給出要移位的位數。如果第二個運算元是正數,則向左移位;否則向右移位。移入係數的數字是零。第一個運算元的符號和指數不變。
- sqrt(context=None)¶
回傳引數的完整精度平方根。
- to_eng_string(context=None)¶
轉換為字串,如果需要指數則使用工程記號。
工程記號的指數是 3 的倍數。這可能在小數點左側留下最多 3 位數字,並可能需要添加一個或兩個尾隨零。
例如,這將
Decimal('123E+1')轉換為Decimal('1.23E+3')。
- to_integral(rounding=None, context=None)¶
與
to_integral_value()方法相同。保留to_integral名稱是為了與舊版本相容。
- to_integral_exact(rounding=None, context=None)¶
捨入到最近的整數,如果發生捨入則適當地發出
Inexact或Rounded訊號。捨入模式由rounding參數決定(如果有提供),否則由給定的context決定。如果兩個參數都沒有提供,則使用目前情境的捨入模式。
- to_integral_value(rounding=None, context=None)¶
捨入到最近的整數,不發出
Inexact或Rounded訊號。如果有提供,套用 rounding;否則,使用提供的 context 或目前情境中的捨入方法。
Decimal 數字可以使用
round()函式進行捨入:- round(number)
- round(number, ndigits)
如果沒有提供 ndigits 或為
None,回傳最接近 number 的int,平局時捨入到偶數,並忽略Decimal情境的捨入模式。如果 number 是無窮大則引發OverflowError,如果是(安靜或發出訊號的)NaN 則引發ValueError。如果 ndigits 是
int,會遵守情境的捨入模式,並回傳代表 number 捨入到Decimal('1E-ndigits')最近倍數的Decimal;在這種情況下,round(number, ndigits)等價於self.quantize(Decimal('1E-ndigits'))。如果 number 是安靜 NaN 則回傳Decimal('NaN')。如果 number 是無窮大、發出訊號的 NaN,或 quantize 運算後係數的長度大於目前情境的精度,則引發InvalidOperation。換句話說,對於非邊界情況:如果 ndigits 是正數,回傳 number 捨入到 ndigits 小數位數;
如果 ndigits 是零,回傳 number 捨入到最近的整數;
如果 ndigits 是負數,回傳 number 捨入到
10**abs(ndigits)的最近倍數。
舉例來說:
>>> from decimal import Decimal, getcontext, ROUND_DOWN >>> getcontext().rounding = ROUND_DOWN >>> round(Decimal('3.75')) # 忽略情境捨入 4 >>> round(Decimal('3.5')) # 平局捨入到偶數 4 >>> round(Decimal('3.75'), 0) # 使用情境捨入 Decimal('3') >>> round(Decimal('3.75'), 1) Decimal('3.7') >>> round(Decimal('3.75'), -1) Decimal('0E+1')
邏輯運算元¶
logical_and()、logical_invert()、logical_or() 和 logical_xor() 方法期望它們的引數是 logical operands。logical operand 是指數和符號都為零,且數字全部為 0 或 1 的 Decimal 實例。
Context 物件¶
Context 是算術運算的環境。它們控制精度、設定捨入規則、決定哪些訊號被視為例外,並限制指數的範圍。
每個執行緒都有自己的目前情境,可以使用 getcontext() 和 setcontext() 函式來存取或變更:
- decimal.getcontext()¶
回傳作用中執行緒的目前情境。
- decimal.setcontext(c, /)¶
將作用中執行緒的目前情境設定為 c。
你也可以使用 with 陳述式和 localcontext() 函式來暫時變更作用中的情境。
- decimal.localcontext(ctx=None, **kwargs)¶
回傳一個情境管理器,在進入 with 陳述式時會將作用中執行緒的目前情境設定為 ctx 的副本,並在退出 with 陳述式時恢復先前的情境。如果沒有指定情境,則使用目前情境的副本。kwargs 引數用於設定新情境的屬性。
例如,以下程式碼將目前的 decimal 精度設定為 42 位,執行計算,然後自動恢復先前的情境:
from decimal import localcontext with localcontext() as ctx: ctx.prec = 42 # 執行高精度計算 s = calculate_something() s = +s # 將最終結果捨入回預設精度
使用關鍵字引數,程式碼如下:
from decimal import localcontext with localcontext(prec=42) as ctx: s = calculate_something() s = +s
如果 kwargs 提供了
Context不支援的屬性,則引發TypeError。如果 kwargs 為屬性提供了無效值,則引發TypeError或ValueError。在 3.11 版的變更:
localcontext()現在支援透過關鍵字引數來設定 context 屬性。
- decimal.IEEEContext(bits)¶
回傳一個 context 物件,其值被初始化為某個 IEEE 交換格式的適當值。引數必須是 32 的倍數且小於
IEEE_CONTEXT_MAX_BITS。在 3.14 版被加入.
新的 context 也可以使用下面描述的 Context 建構函式來建立。此外,該模組提供了三個預製的 context:
- decimal.BasicContext¶
這是由通用十進位算術規格定義的標準 context。精度設定為九。捨入設定為
ROUND_HALF_UP。所有旗標都被清除。所有的 trap 都被啟用(視為例外),除了Inexact、Rounded和Subnormal。因為很多 trap 都被啟用,這個 context 對於除錯很有用。
- decimal.ExtendedContext¶
這是由通用十進位算術規格定義的標準 context。精度設定為九。捨入設定為
ROUND_HALF_EVEN。所有旗標都被清除。沒有 trap 被啟用(因此在運算過程中不會引發例外)。因為 trap 被停用,這個 context 對於傾向於拿到
NaN或Infinity結果值而不是引發例外的應用程式很有用。這讓應用程式在存在原本會中止程式的條件時也能完成執行。
- decimal.DefaultContext¶
這個 context 被
Context建構函式用作新 context 的原型。改變一個欄位(例如精度)的效果是改變Context建構函式建立的新 context 的預設值。這個 context 在多執行緒環境中最有用。在執行緒啟動之前改變其中一個欄位的效果是設定系統級預設值。不建議在執行緒啟動後改變欄位,因為這會需要執行緒同步來防止競爭條件。
在單執行緒環境中,最好完全不使用這個 context。取而代之,只需按下面描述的方式明確建立 context。
預設值為
Context.prec=28、Context.rounding=ROUND_HALF_EVEN,並啟用Overflow、InvalidOperation和DivisionByZero的 trap。
除了這三個提供的 context 之外,還可以使用 Context 建構函式建立新的 context。
- class decimal.Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None)¶
建立一個新的 context。如果欄位未指定或為
None,則從DefaultContext複製預設值。如果 flags 欄位未指定或為None,則清除所有旗標。- rounding¶
Rounding Modes 段落中列出的常數之一。
- capitals¶
0或1(預設)。如果設定為1,指數用大寫E列印;否則,使用小寫e:Decimal('6.02e+23')。
- clamp¶
0(預設)或1。如果設定為1,在此 context 中可表示的Decimal實例的指數e嚴格限制在Emin - prec + 1 <= e <= Emax - prec + 1範圍內。如果 clamp 為0,則適用較弱的條件:Decimal實例的調整指數最多為Emax。當 clamp 為1時,如果可能的話,大的正規數字會減少其指數並在其係數上添加相應數量的零,以符合指數約束;這保留了數字的值,但會失去有關有效尾隨零的資訊。例如:>>> Context(prec=6, Emax=999, clamp=1).create_decimal('1.23e999') Decimal('1.23000E+999')
clamp 值為
1可以與 IEEE 754 中指定的固定寬度十進位交換格式相容。
Context類別定義了幾個通用方法,以及大量在給定 context 中直接進行算術運算的方法。此外,對於上面描述的每個Decimal方法(除了adjusted()和as_tuple()方法之外),都有相對應的Context方法。例如,對於Context實例C和Decimal實例x,C.exp(x)等價於x.exp(context=C)。每個Context方法在接受 Decimal 實例的任何地方都接受 Python 整數(int的實例)。- clear_flags()¶
將所有旗標重設為
0。
- clear_traps()¶
將所有 trap 重設為
0。在 3.3 版被加入.
- copy()¶
回傳 context 的一個副本。
Contextobjects also supportcopy.replace(), which returns a duplicate with the specified fields replaced. Fields which are not specified keep the values they have in the original context.在 3.16 版的變更: Added support for
copy.replace().
- copy_decimal(num, /)¶
回傳 Decimal 實例 num 的副本。
- create_decimal(num='0', /)¶
從 num 建立一個新的 Decimal 實例,但使用 self 作為 context。與
Decimal建構函式不同,context 精度、捨入方法、旗標和 trap 會套用於轉換。這很有用,因為常數通常會提供比應用程式需要的更高精度。另一個好處是捨入立即消除超出當前精度的數字的意外影響。在以下範例中,使用未捨入的輸入意味著在總和中加零可能會改變結果:
>>> getcontext().prec = 3 >>> Decimal('3.4445') + Decimal('1.0023') Decimal('4.45') >>> Decimal('3.4445') + Decimal(0) + Decimal('1.0023') Decimal('4.44')
此方法實作了 IBM 規格的 to-number 操作。如果引數是字串,則不允許前導或尾隨的空白字元或底線。
- create_decimal_from_float(f, /)¶
從浮點數 f 建立一個新的 Decimal 實例,但使用 self 作為 context 進行四捨五入。與
Decimal.from_float()類別方法不同,context 精度、捨入方法、旗標和 trap 會套用於轉換。>>> context = Context(prec=5, rounding=ROUND_DOWN) >>> context.create_decimal_from_float(math.pi) Decimal('3.1415') >>> context = Context(prec=5, traps=[Inexact]) >>> context.create_decimal_from_float(math.pi) Traceback (most recent call last): ... decimal.Inexact: None
在 3.1 版被加入.
- Etop()¶
回傳一個等於
Emax - prec + 1的值。
處理 decimal 的通常方法是建立
Decimal實例,然後在活動執行緒的當前 context 中進行算術運算。另一種方法是使用 context 方法在特定 context 中進行計算。這些方法與Decimal類別的方法相似,在此僅簡要重述。- abs(x, /)¶
回傳 x 的絕對值。
- add(x, y, /)¶
回傳 x 與 y 的和。
- canonical(x, /)¶
回傳相同的 Decimal 物件 x。
- compare(x, y, /)¶
數值比較 x 和 y。
- compare_signal(x, y, /)¶
數值比較兩個運算元的值。
- compare_total(x, y, /)¶
使用兩個運算元的抽象表示法進行比較。
- compare_total_mag(x, y, /)¶
使用兩個運算元的抽象表示法進行比較,忽略符號。
- copy_abs(x, /)¶
回傳 x 的副本,將符號設定為 0。
- copy_negate(x, /)¶
回傳 x 的副本,將符號反轉。
- copy_sign(x, y, /)¶
將 y 的正負號複製到 x。
- divide(x, y, /)¶
回傳 x 除以 y。
- divide_int(x, y, /)¶
回傳 x 除以 y,截斷為整數。
- divmod(x, y, /)¶
將兩個數相除並回傳結果的整數部分。
- exp(x, /)¶
回傳
e ** x。
- fma(x, y, z, /)¶
回傳 x 乘以 y,再加上 z。
- is_canonical(x, /)¶
如果 x 是標準形式則回傳
True;否則回傳False。
- is_finite(x, /)¶
如果 x 是有限的則回傳
True;否則回傳False。
- is_infinite(x, /)¶
如果 x 是無限大則回傳
True;否則回傳False。
- is_nan(x, /)¶
如果 x 是 qNaN 或 sNaN 則回傳
True;否則回傳False。
- is_normal(x, /)¶
如果 x 是正規數 (normal number) 則回傳
True;否則回傳False。
- is_qnan(x, /)¶
如果 x 是安靜型 NaN (quiet NaN) 則回傳
True;否則回傳False。
- is_signed(x, /)¶
如果 x 是負數則回傳
True;否則回傳False。
- is_snan(x, /)¶
如果 x 是訊號型 NaN (signaling NaN) 則回傳
True;否則回傳False。
- is_subnormal(x, /)¶
如果 x 是次正規數 (subnormal) 則回傳
True;否則回傳False。
- is_zero(x, /)¶
如果 x 是零則回傳
True;否則回傳False。
- ln(x, /)¶
回傳 x 的自然對數(以 e 為底)。
- log10(x, /)¶
回傳 x 的以 10 為底的對數。
- logb(x, /)¶
回傳運算元最高有效位數(MSD)數值的指數。
- logical_and(x, y, /)¶
將每個運算元的位數套用邏輯運算 and。
- logical_invert(x, /)¶
反轉 x 中的所有數字。
- logical_or(x, y, /)¶
在每個運算元的數字之間套用邏輯運算 or。
- logical_xor(x, y, /)¶
在每個運算元的數字之間套用邏輯運算 xor。
- max(x, y, /)¶
數值比較兩個值並回傳最大值。
- max_mag(x, y, /)¶
數值比較值,忽略其符號。
- min(x, y, /)¶
數值比較兩個值並回傳最小值。
- min_mag(x, y, /)¶
數值比較值,忽略其符號。
- minus(x, /)¶
Minus 對應於 Python 中的一元前綴減號運算子。
- multiply(x, y, /)¶
回傳 x 和 y 的乘積。
- next_minus(x, /)¶
回傳小於 x 的最大可表示數字。
- next_plus(x, /)¶
回傳大於 x 的最小可表示數字。
- next_toward(x, y, /)¶
回傳最接近 x 的數字,方向指向 y。
- normalize(x, /)¶
將 x 簡化為最簡形式。
- number_class(x, /)¶
回傳 x 類別的指示。
- plus(x, /)¶
Plus 對應於 Python 中的一元前綴加號運算子。此運算會套用 context 精度和捨入,因此它 不是 一個恆等運算。
- power(x, y, modulo=None)¶
回傳
x的y次方;如果有提供modulo,則回傳對modulo取模後的結果。使用兩個引數計算
x**y。如果x是負數,則y必須是整數。除非y是整數且結果是有限的且可以在「精度」位數中精確表示,否則結果將是不精確的。使用 context 的捨入模式。在 Python 版本中,結果總是正確捨入的。Decimal(0) ** Decimal(0)會導致InvalidOperation,如果InvalidOperation沒有被捕捉,則會產生Decimal('NaN')。使用三個引數計算
(x**y) % modulo。對於三個引數的形式,引數有以下限制:所有三個引數必須是整數
y必須是非負數x或y中至少一個必須是非零modulo必須是非零且最多有「精度」位數
Context.power(x, y, modulo)的結果值等於以無限精度計算(x**y) % modulo所得到的值,但計算更加高效。結果的指數是零,無論x、y和modulo的指數如何。結果總是精確的。
- quantize(x, y, /)¶
回傳等於 x*(捨入後)的值,具有 *y 的指數。
- radix()¶
只回傳 10,因為這是 Decimal :)
- remainder(x, y, /)¶
回傳整數除法的餘數。
結果的符號(如果非零)與原始被除數的符號相同。
- remainder_near(x, y, /)¶
回傳
x - y * n,其中 n 是最接近x / y精確值的整數(如果結果是 0,則其符號將是 x 的符號)。
- rotate(x, y, /)¶
回傳 x 的旋轉副本,旋轉 y 次。
- same_quantum(x, y, /)¶
如果兩個運算元有相同的指數則回傳
True。
- scaleb(x, y, /)¶
回傳第一個運算元在加上第二個值的 exp 後的結果。
- shift(x, y, /)¶
回傳 x 的移位副本,移位 y 次。
- sqrt(x, /)¶
非負數在 context 精度下的平方根。
- subtract(x, y, /)¶
回傳 x 和 y 的差。
- to_eng_string(x, /)¶
轉換為字串,如果需要指數則使用工程記號。
工程記號的指數是 3 的倍數。這可能在小數點左側留下最多 3 位數字,並可能需要添加一個或兩個尾隨零。
- to_integral_exact(x, /)¶
捨入為整數。
- to_sci_string(x, /)¶
使用科學記號法將數字轉換為字串。
常數¶
- decimal.SPEC_VERSION¶
The highest version of the General Decimal Arithmetic Specification that this implementation complies with. See https://speleotrove.com/decimal/decarith.html for the specification.
在 3.15 版被加入.
The following constants are only relevant for the C module. They are also included in the pure Python version for compatibility.
32 位元 |
64 位元 |
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- decimal.HAVE_THREADS¶
值是
True。已棄用,因為 Python 現在總是有執行緒。在 3.9 版之後被棄用.
- decimal.HAVE_CONTEXTVAR¶
預設值是
True。如果 Python 是使用--without-decimal-contextvar 選項配置的,C 版本使用執行緒區域而非協程區域 context,且值為False。在某些巢狀 context 情境中這稍微快一些。在 3.8.3 版被加入.
The following constants are only available in the C module.
- decimal.LIBMPDEC_VERSION¶
The version string of the libmpdec library that was used for building the module. This may be different from the libmpdec library actually used at runtime, which is available as
libmpdec_version.在 3.16 版被加入.
- decimal.libmpdec_version¶
The version string of the libmpdec library actually loaded by the interpreter.
在 3.16 版被加入.
- decimal.LIBMPDEC_VERSION_INFO¶
A named tuple containing the three components of the libmpdec library version that was used for building the module: major, minor, and micro. All values are integers. The components can also be accessed by name, so
decimal.LIBMPDEC_VERSION_INFO[0]is equivalent todecimal.LIBMPDEC_VERSION_INFO.majorand so on. This may be different from the libmpdec library actually used at runtime, which is available aslibmpdec_version_info.在 3.16 版被加入.
- decimal.libmpdec_version_info¶
A named tuple containing the version of the libmpdec library actually loaded by the interpreter, with the same fields as
LIBMPDEC_VERSION_INFO.在 3.16 版被加入.
捨入模式¶
- decimal.ROUND_CEILING¶
向
Infinity捨入。
- decimal.ROUND_DOWN¶
向零捨入。
- decimal.ROUND_FLOOR¶
向
-Infinity捨入。
- decimal.ROUND_HALF_DOWN¶
捨入到最近值,平局時向零捨入。
- decimal.ROUND_HALF_EVEN¶
捨入到最近值,平局時向最近的偶數捨入。
- decimal.ROUND_HALF_UP¶
捨入到最近值,平局時遠離零捨入。
- decimal.ROUND_UP¶
遠離零捨入。
- decimal.ROUND_05UP¶
如果向零捨入後的最後一位數字為 0 或 5,則遠離零捨入;否則向零捨入。
訊號¶
訊號代表在計算過程中產生的條件。每個訊號對應一個情境旗標和一個情境陷阱啟用器。
每當遇到條件時,情境旗標就會被設定。計算後,可以檢查旗標以獲取資訊(例如,確定計算是否精確)。檢查旗標後,請確保在開始下一次計算之前清除所有旗標。
如果為訊號設定了情境的陷阱啟用器,那麼條件會導致引發 Python 例外。例如,如果設定了 DivisionByZero 陷阱,那麼在遇到條件時會引發 DivisionByZero 例外。
- class decimal.DecimalException¶
其他訊號的基底類別,也是
ArithmeticError的子類別。
- class decimal.DivisionByZero¶
發出非無限數被零除的訊號。
可能發生在除法、模除法或將數字提升到負次方時。如果此訊號未被捕捉,回傳
Infinity或-Infinity,符號由計算的輸入決定。
- class decimal.Inexact¶
指示發生了捨入且結果不精確。
當捨入過程中丟棄非零數字時發出訊號。回傳捨入結果。訊號旗標或陷阱用於檢測結果何時不精確。
- class decimal.InvalidOperation¶
執行了無效的運算。
指示請求了不合理的運算。如果未被捕捉,回傳
NaN。可能的原因包括:Infinity - Infinity 0 * Infinity Infinity / Infinity x % 0 Infinity % x sqrt(-x) and x > 0 0 ** 0 x ** (non-integer) x ** Infinity
- class decimal.Overflow¶
數值溢位。
指示在捨入發生後指數大於
Context.Emax。如果未被捕捉,結果取決於捨入模式,要麼向內拉到最大可表示的有限數,要麼向外捨入到Infinity。在任何情況下,Inexact和Rounded也會發出訊號。
- class decimal.Rounded¶
發生了捨入,但可能沒有遺失資訊。
當捨入丟棄數字時發出訊號;即使這些數字是零(例如將
5.00捨入為5.0)。如果沒有被捕捉,則回傳不變的結果。此訊號用於檢測有效數字的遺失。
- class decimal.FloatOperation¶
啟用混合 float 和 Decimal 的更嚴格語意。
如果訊號沒有被捕捉(預設),則在
Decimal建構函式、create_decimal()和所有比較運算子中允許混合 float 和 Decimal。轉換和比較都是精確的。任何混合運算的發生都會透過在 context 旗標中設定FloatOperation來默默記錄。使用from_float()或create_decimal_from_float()的明確轉換不會設定標誌。否則(訊號被捕捉),只有相等比較和明確轉換是安靜的。所有其他混合運算都會引發
FloatOperation。
以下表格總結了訊號的階層結構:
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)
浮點數注意事項¶
透過增加精度來減輕捨入誤差¶
使用十進位浮點數消除了十進位表示誤差(使得可以精確表示 0.1);然而,當非零數字超過固定精度時,某些運算仍然可能產生捨入誤差。
捨入誤差的影響可能會因為幾乎抵消的數量的加法或減法而被放大,導致有效位數的損失。Knuth 提供了兩個教學範例,其中精度不足的捨入浮點運算導致加法的結合律和分配律失效:
# 來自 Seminumerical Algorithms 第 4.2.2 節的範例。
>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 8
>>> u, v, w = Decimal(11111113), Decimal(-11111111), Decimal('7.51111111')
>>> (u + v) + w
Decimal('9.5111111')
>>> u + (v + w)
Decimal('10')
>>> u, v, w = Decimal(20000), Decimal(-6), Decimal('6.0000003')
>>> (u*v) + (u*w)
Decimal('0.01')
>>> u * (v+w)
Decimal('0.0060000')
decimal 模組使得透過充分擴展精度來避免有效位數損失,從而恢復恆等式成為可能:
>>> getcontext().prec = 20
>>> u, v, w = Decimal(11111113), Decimal(-11111111), Decimal('7.51111111')
>>> (u + v) + w
Decimal('9.51111111')
>>> u + (v + w)
Decimal('9.51111111')
>>>
>>> u, v, w = Decimal(20000), Decimal(-6), Decimal('6.0000003')
>>> (u*v) + (u*w)
Decimal('0.0060000')
>>> u * (v+w)
Decimal('0.0060000')
特殊值¶
decimal 模組的數字系統提供特殊值,包括 NaN、sNaN、-Infinity、Infinity 以及兩個零 +0 和 -0。
無窮大可以直接透過 Decimal('Infinity') 建構。此外,當 DivisionByZero 訊號未被捕捉時,它們可能來自被零除。同樣地,當 Overflow 訊號未被捕捉時,無窮大可能來自超出最大可表示數字限制的捨入。
無窮大是有符號的(仿射)並且可以用於算術運算,在那裡它們被視為非常大的不確定數字。例如,將常數加到無窮大會得到另一個無限結果。
某些運算是不確定的且會回傳 NaN,或者如果 InvalidOperation 訊號被捕捉,則會引發例外。例如,0/0 會回傳 NaN,意思是「非數字」。這種 NaN 是安靜的,一旦建立,會流經其他計算且總是產生另一個 NaN。這種行為對於偶爾有缺失輸入的一系列計算很有用——它允許計算繼續進行,同時將特定結果標記為無效。
一個變體是 sNaN,它在每次運算後發出訊號而不是保持安靜。當無效結果需要中斷計算進行特殊處理時,這是一個有用的回傳值。
當涉及 NaN 時,Python 比較運算子的行為可能有點令人驚訝。當其中一個運算元是安靜或發出訊號的 NaN 時,相等性測試總是回傳 False(即使在執行 Decimal('NaN')==Decimal('NaN') 時也是如此),而不等性測試總是回傳 True。嘗試使用任何 <、<=、> 或 >= 運算子比較兩個 Decimal 時,如果任一運算元是 NaN,將引發 InvalidOperation 訊號,如果此訊號沒有被捕捉則回傳 False。請注意,通用十進位算術規格沒有指定直接比較的行為;這些涉及 NaN 比較的規則取自 IEEE 854 標準(參見第 5.7 節的表 3)。為確保嚴格符合標準,請改用 compare() 和 compare_signal() 方法。
帶符號的零可能來自下溢計算。它們保持如果計算以更高精度執行所會產生的符號。由於它們的大小是零,正零和負零都被視為相等,它們的符號是資訊性的。
除了兩個不同但相等的帶符號零之外,還有各種零的表示方式,它們有不同的精度但值相等。這需要一點時間來適應。對於習慣於標準化浮點表示的眼睛來說,以下計算回傳等於零的值並不立即明顯:
>>> 1 / Decimal('Infinity')
Decimal('0E-1000026')
與執行緒協作¶
getcontext() 函式為每個執行緒存取不同的 Context 物件。擁有獨立的執行緒情境意味著執行緒可以進行變更(如 getcontext().prec=10)而不會干擾其他執行緒。
同樣地,setcontext() 函式會自動將其目標指派給目前執行緒。
如果在 getcontext() 之前沒有呼叫 setcontext(),則 getcontext() 會自動建立新的 context 供目前執行緒使用。新的 context 物件的預設值取自 decimal.DefaultContext 物件。
sys.flags.thread_inherit_context 旗標會影響新執行緒的 context。如果該旗標為 false,新執行緒會以空的 context 啟動。在這種情況下,getcontext() 在被呼叫時會建立新的 context 物件,並使用 DefaultContext 的預設值。如果該旗標為 true,新執行緒會以呼叫 threading.Thread.start() 的執行緒的 context 副本啟動。
要控制預設值以便每個執行緒在整個應用程式中使用相同的值,請直接修改 DefaultContext 物件。這應該在任何執行緒啟動之前完成,這樣執行緒呼叫 getcontext() 時就不會有競爭條件。例如:
# 為即將啟動的所有執行緒設定應用程式範圍的預設值
DefaultContext.prec = 12
DefaultContext.rounding = ROUND_DOWN
DefaultContext.traps = ExtendedContext.traps.copy()
DefaultContext.traps[InvalidOperation] = 1
setcontext(DefaultContext)
# 之後,可以啟動執行緒
t1.start()
t2.start()
t3.start()
. . .
實用範例¶
以下是一些實用範例,作為工具函式並展示與 Decimal 類別協作的方法:
def moneyfmt(value, places=2, curr='', sep=',', dp='.',
pos='', neg='-', trailneg=''):
"""將 Decimal 轉換為貨幣格式的字串。
places: 小數點後所需的位數
curr: 符號前的選擇性貨幣符號(可為空白)
sep: 選擇性的分組分隔符號(逗號、句號、空格或空白)
dp: 小數點指示符號(逗號或句號)
僅在 places 為零時指定為空白
pos: 正數的選擇性符號:'+'、空格或空白
neg: 負數的選擇性符號:'-'、'('、空格或空白
trailneg:選擇性的尾隨負號指示符號:'-'、')'、空格或空白
>>> d = Decimal('-1234567.8901')
>>> moneyfmt(d, curr='$')
'-$1,234,567.89'
>>> moneyfmt(d, places=0, sep='.', dp='', neg='', trailneg='-')
'1.234.568-'
>>> moneyfmt(d, curr='$', neg='(', trailneg=')')
'($1,234,567.89)'
>>> moneyfmt(Decimal(123456789), sep=' ')
'123 456 789.00'
>>> moneyfmt(Decimal('-0.02'), neg='<', trailneg='>')
'<0.02>'
"""
q = Decimal(10) ** -places # 2 個位數 --> '0.01'
sign, digits, exp = value.quantize(q).as_tuple()
result = []
digits = list(map(str, digits))
build, next = result.append, digits.pop
if sign:
build(trailneg)
for i in range(places):
build(next() if digits else '0')
if places:
build(dp)
if not digits:
build('0')
i = 0
while digits:
build(next())
i += 1
if i == 3 and digits:
i = 0
build(sep)
build(curr)
build(neg if sign else pos)
return ''.join(reversed(result))
def pi():
"""計算 Pi 到目前的精度。
>>> print(pi())
3.141592653589793238462643383
"""
getcontext().prec += 2 # 中間步驟的額外位數
three = Decimal(3) # 用 "three=3.0" 替換為一般浮點數
lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24
while s != lasts:
lasts = s
n, na = n+na, na+8
d, da = d+da, da+32
t = (t * n) / d
s += t
getcontext().prec -= 2
return +s # 一元正號套用新的精度
def exp(x):
"""回傳 e 的 x 次方。結果型別與輸入型別相同。
>>> print(exp(Decimal(1)))
2.718281828459045235360287471
>>> print(exp(Decimal(2)))
7.389056098930650227230427461
>>> print(exp(2.0))
7.38905609893
>>> print(exp(2+0j))
(7.38905609893+0j)
"""
getcontext().prec += 2
i, lasts, s, fact, num = 0, 0, 1, 1, 1
while s != lasts:
lasts = s
i += 1
fact *= i
num *= x
s += num / fact
getcontext().prec -= 2
return +s
def cos(x):
"""回傳以弧度為單位的 x 的餘弦值。
泰勒級數近似在 x 值較小時效果最佳。
對於較大的值,先計算 x = x % (2 * pi)。
>>> print(cos(Decimal('0.5')))
0.8775825618903727161162815826
>>> print(cos(0.5))
0.87758256189
>>> print(cos(0.5+0j))
(0.87758256189+0j)
"""
getcontext().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
getcontext().prec -= 2
return +s
def sin(x):
"""回傳以弧度為單位的 x 的正弦值。
泰勒級數近似在 x 值較小時效果最佳。
對於較大的值,先計算 x = x % (2 * pi)。
>>> print(sin(Decimal('0.5')))
0.4794255386042030002732879352
>>> print(sin(0.5))
0.479425538604
>>> print(sin(0.5+0j))
(0.479425538604+0j)
"""
getcontext().prec += 2
i, lasts, s, fact, num, sign = 1, 0, x, 1, x, 1
while s != lasts:
lasts = s
i += 2
fact *= i * (i-1)
num *= x * x
sign *= -1
s += num / fact * sign
getcontext().prec -= 2
return +s
Decimal 常見問題¶
問:輸入 decimal.Decimal('1234.5') 很麻煩。在使用互動式直譯器時,有沒有辦法減少輸入?
答:有些使用者會將建構式縮寫為單一字母:
>>> D = decimal.Decimal
>>> D('1.23') + D('3.45')
Decimal('4.68')
問:在一個有兩位小數的定點應用程式中,有些輸入有很多位數需要被捨入。其他輸入不應該有多餘的位數,需要被驗證。應該使用什麼方法?
答:quantize() 方法會捨入到固定的小數位數。如果設定了 Inexact 陷阱,它也可用於驗證:
>>> TWOPLACES = Decimal(10) ** -2 # same as Decimal('0.01')
>>> # Round to two places
>>> Decimal('3.214').quantize(TWOPLACES)
Decimal('3.21')
>>> # Validate that a number does not exceed two places
>>> Decimal('3.21').quantize(TWOPLACES, context=Context(traps=[Inexact]))
Decimal('3.21')
>>> Decimal('3.214').quantize(TWOPLACES, context=Context(traps=[Inexact]))
Traceback (most recent call last):
...
Inexact: None
問:一旦我有了有效的兩位小數輸入,如何在整個應用程式中維持這個不變式?
答:某些運算如加法、減法和整數乘法會自動保留定點。其他運算,例如除法和非整數乘法,會改變小數位數,需要接著執行 quantize() 步驟:
>>> a = Decimal('102.72') # Initial fixed-point values
>>> b = Decimal('3.17')
>>> a + b # Addition preserves fixed-point
Decimal('105.89')
>>> a - b
Decimal('99.55')
>>> a * 42 # So does integer multiplication
Decimal('4314.24')
>>> (a * b).quantize(TWOPLACES) # Must quantize non-integer multiplication
Decimal('325.62')
>>> (b / a).quantize(TWOPLACES) # And quantize division
Decimal('0.03')
在開發定點應用程式時,定義函式來處理 quantize() 步驟很方便:
>>> def mul(x, y, fp=TWOPLACES):
... return (x * y).quantize(fp)
...
>>> def div(x, y, fp=TWOPLACES):
... return (x / y).quantize(fp)
>>> mul(a, b) # Automatically preserve fixed-point
Decimal('325.62')
>>> div(b, a)
Decimal('0.03')
問:有很多方式可以表達相同的值。數字 200、200.000、2E2 和 .02E+4 在不同精度下都有相同的值。有沒有辦法將它們轉換為單一可識別的標準值?
答:normalize() 方法會將所有等價的值對映到單一代表值:
>>> values = map(Decimal, '200 200.000 2E2 .02E+4'.split())
>>> [v.normalize() for v in values]
[Decimal('2E+2'), Decimal('2E+2'), Decimal('2E+2'), Decimal('2E+2')]
問:在計算中何時會發生捨入?
答:它發生在計算之後。decimal 規格的哲學是數字被視為精確的,且獨立於目前的 context 建立。它們甚至可以有比目前 context 更高的精度。計算使用那些精確的輸入進行處理,然後捨入(或其他 context 運算)被套用到計算的結果上:
>>> getcontext().prec = 5
>>> pi = Decimal('3.1415926535') # 超過 5 位數
>>> pi # 保留所有數字
Decimal('3.1415926535')
>>> pi + 0 # 加法後捨入
Decimal('3.1416')
>>> pi - Decimal('0.00005') # 減去未捨入的數字,然後捨入
Decimal('3.1415')
>>> pi + 0 - Decimal('0.00005'). # 中間值被捨入
Decimal('3.1416')
問:有些 decimal 值總是以指數表示法印出。有沒有辦法得到非指數表示法?
答:對於某些值,指數表示法是表達係數中有效位數的唯一方式。例如,將 5.0E+3 表示為 5000 保持了值不變,但無法顯示原始值的兩位有效性。
如果應用程式不關心追蹤有效性,很容易移除指數和尾隨零,遺失有效性,但保持值不變:
>>> def remove_exponent(d):
... return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
>>> remove_exponent(Decimal('5E+3'))
Decimal('5000')
問:有沒有辦法將一般的 float 轉換為 Decimal?
答:是的,任何二進位浮點數都可以精確地表示為 Decimal,儘管精確轉換可能需要比直覺所預期的更高精度:
>>> Decimal(math.pi)
Decimal('3.141592653589793115997963468544185161590576171875')
問:在複雜計算中,如何確保我沒有因為精度不足或捨入異常而得到虛假的結果?
答:decimal 模組使測試結果變得容易。最佳實踐是使用更高的精度和各種捨入模式重新執行計算。結果差異很大表示精度不足、捨入模式問題、病態輸入或數值不穩定的演算法。
問:我注意到 context 精度被套用到運算的結果上,但不套用到輸入上。在混合不同精度的值時,有什麼需要注意的嗎?
答:是的。原則是所有值都被視為精確的,對這些值的算術也是如此。只有結果會被捨入。對於輸入的好處是「你輸入什麼就得到什麼」。缺點是如果你忘記輸入沒有被捨入,結果可能看起來很奇怪:
>>> getcontext().prec = 3
>>> Decimal('3.104') + Decimal('2.104')
Decimal('5.21')
>>> Decimal('3.104') + Decimal('0.000') + Decimal('2.104')
Decimal('5.20')
解決方案是增加精度或使用一元加號運算強制輸入捨入:
>>> getcontext().prec = 3
>>> +Decimal('1.23456789') # 一元正號觸發捨入
Decimal('1.23')
或者,可以使用 Context.create_decimal() 方法在建立時將輸入捨入:
>>> Context(prec=5, rounding=ROUND_DOWN).create_decimal('1.2345678')
Decimal('1.2345')
問:CPython 實作對大數運算快嗎?
答:是的。在 CPython 和 PyPy3 實作中,decimal 模組的 C/CFFI 版本整合了高速 libmpdec 函式庫,用於任意精度正確捨入的十進位浮點運算 [1]。libmpdec 對中等大小的數字使用 Karatsuba 乘法,對非常大的數字使用數論轉換。
context 必須適應精確的任意精度運算。Emin 和 Emax 應該始終設定為最大值,clamp 應該始終為 0(預設)。設定 prec 需要一些小心。
嘗試大數運算的最簡單方法是同時使用 prec 的最大值 [2]:
>>> setcontext(Context(prec=MAX_PREC, Emax=MAX_EMAX, Emin=MIN_EMIN))
>>> x = Decimal(2) ** 256
>>> x / 128
Decimal('904625697166532776746648320380374280103671755200316906558262375061821325312')
對於不精確的結果,MAX_PREC 在 64 位元平台上太大了,可用記憶體將不足:
>>> Decimal(1) / 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
MemoryError
在具有過度分配的系統上(例如 Linux),更精密的方法是將 prec 調整為可用 RAM 的數量。假設你有 8GB 的 RAM,並且預期 10 個同時操作數,每個最多使用 500MB:
>>> import sys
>>>
>>> # 使用 8 位元組字組中 500MB 的單一操作數最大位數
>>> # 每個字組 19 位數(32 位元組建中為 4 位元組和 9 位數):
>>> maxdigits = 19 * ((500 * 1024**2) // 8)
>>>
>>> # 檢查這是否有效:
>>> c = Context(prec=maxdigits, Emax=MAX_EMAX, Emin=MIN_EMIN)
>>> c.traps[Inexact] = True
>>> setcontext(c)
>>>
>>> # 用九填滿可用的精度:
>>> x = Decimal(0).logical_invert() * 9
>>> sys.getsizeof(x)
524288112
>>> x + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.Inexact: [<class 'decimal.Inexact'>]
一般來說(特別是在沒有過度分配的系統上),建議估算更緊密的界限並設定 Inexact trap,如果所有計算都預期是精確的。