decimal --- 十進位制定點和浮點運算

原始碼: Lib/decimal.py


decimal 模組提供了快速且正確的舍入十進位制浮點運算支援。與 float 資料型別相比,它有幾個優點。

  • Decimal “基於一種為人類設計的浮點模型,其首要的指導原則是 —— 計算機必須提供一種與人們在學校學習的算術相同的算術。” —— 摘自十進位制算術規範。

  • Decimal 數字可以被精確地表示。相比之下,像 1.12.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 還包括特殊值,如 Infinity-InfinityNaN。標準還區分了 -0+0

算術上下文是一個指定精度、舍入規則、指數限制、指示操作結果的標誌位以及決定訊號是否被視為異常的陷阱啟用器的環境。舍入選項包括 ROUND_CEILINGROUND_DOWNROUND_FLOORROUND_HALF_DOWNROUND_HALF_EVENROUND_HALF_UPROUND_UPROUND_05UP

訊號是在計算過程中出現的一組異常條件。根據應用程式的需要,訊號可以被忽略、視為資訊性內容或作為異常處理。decimal 模組中的訊號有:ClampedInvalidOperationDivisionByZeroInexactRoundedSubnormalOverflowUnderflowFloatOperation

每個訊號都有一個標誌位和一個陷阱啟用器。當遇到一個訊號時,其標誌位被設定為 1,然後,如果陷阱啟用器也被設定為 1,就會引發一個異常。標誌位是“粘性”的,因此使用者在監控計算之前需要重置它們。

參見

快速入門教程

通常使用 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       # Set a new precision

Decimal 例項可以從整數、字串、浮點數或元組構造。從整數或浮點數構造會執行該整數或浮點數值的精確轉換。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 的其他大部分功能都很好地協同工作。這裡是一個小型的十進位制浮點飛行表演。

>>> 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 可用。

>>> 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() 建構函式建立備用上下文可能很有用。要啟用備用上下文,請使用 setcontext() 函式。

根據標準,decimal 模組提供了兩個現成的標準上下文,BasicContextExtendedContext。前者對除錯特別有用,因為它啟用了許多陷阱。

>>> 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 條目顯示,π 的有理數近似值被舍入了(超出上下文精度的數字被丟棄),並且結果是不精確的(一些被丟棄的數字非零)。

單個陷阱是使用上下文中 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'。大小寫不敏感,因此,例如,infInfINFINITYiNfINity 都是正無窮的可接受拼寫。

如果 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 版更改: 允許使用下劃線進行分組,就像在程式碼中的整數和浮點數字面量一樣。

十進位制浮點物件與其它內建數值型別如 floatint 共享許多屬性。所有常見的數學運算和特殊方法都適用。同樣,decimal 物件可以被複制、pickle、列印、用作字典鍵、用作集合元素、比較、排序以及強制轉換為另一種型別(如 floatint)。

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')

%// 運算子實現了規範中描述的 remainderdivide-integer 操作(分別)。

Decimal 物件通常不能與浮點數或 fractions.Fraction 的例項在算術運算中混合使用:例如,嘗試將一個 Decimal 與一個 float 相加會引發 TypeError。然而,可以使用 Python 的比較運算子來比較一個 Decimal 例項 x 與另一個數 y。這避免了在不同型別的數字之間進行相等性比較時出現混淆的結果。

在 3.2 版更改: Decimal 例項與其他數值型別之間的混合型別比較現在得到完全支援。

除了標準的數值屬性外,十進位制浮點物件還具有許多專門的方法。

adjusted()

返回調整後的指數,該指數是透過移出係數的最右邊數字直到只剩下首位數字得到的:Decimal('321e+5').adjusted() 返回 7。用於確定最高有效位相對於小數點的位置。

as_integer_ratio()

返回一個整數對 (n, d),以最簡分數形式且分母為正數,表示給定的 Decimal 例項。

>>> Decimal('-3.14').as_integer_ratio()
(-157, 50)

轉換是精確的。對於無窮大,引發 OverflowError;對於 NaN,引發 ValueError。

在 3.6 版本加入。

as_tuple()

返回該數字的命名元組表示:DecimalTuple(sign, digits, exponent)

canonical()

返回引數的規範編碼。目前,一個 Decimal 例項的編碼總是規範的,所以此操作返回其引數不變。

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 和信令 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, /)

備選建構函式,僅接受 floatint 的例項。

注意 Decimal.from_float(0.1)Decimal('0.1') 是不同的。由於 0.1 在二進位制浮點數中不能精確表示,該值被儲存為最接近的可表示值,即 0x1.999999999999ap-4。該值在十進位制中的等價值是 0.1000000000000000055511151231257827021181583404541015625

備註

自 Python 3.2 起,Decimal 例項也可以直接從 float 構造。

>>> 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, /)

替代建構函式,只接受 floatintDecimal 的例項,但不接受字串或元組。

>>> 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')
is_canonical()

如果引數是規範的,則返回 True,否則返回 False。目前,一個 Decimal 例項總是規範的,所以此操作總是返回 True

is_finite()

如果引數是有限數,則返回 True;如果引數是無窮大或 NaN,則返回 False

is_infinite()

如果引數是正無窮大或負無窮大,則返回 True,否則返回 False

is_nan()

如果引數是(靜默或信令)NaN,則返回 True,否則返回 False

is_normal(context=None)

如果引數是*正常*有限數,則返回 True。如果引數是零、次正規數、無窮大或 NaN,則返回 False

is_qnan()

如果引數是靜默 NaN,則返回 True,否則返回 False

is_signed()

如果引數帶有負號,則返回 True,否則返回 False。注意,零和 NaN 都可以帶有符號。

is_snan()

如果引數是信令 NaN,則返回 True,否則返回 False

is_subnormal(context=None)

如果引數是次正規數,則返回 True,否則返回 False

is_zero()

如果引數是(正或負)零,則返回 True,否則返回 False

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() 是一個邏輯運算,它接受兩個*邏輯運算元*(參見 邏輯運算元)。結果是兩個運算元的按位 and

logical_invert(context=None)

logical_invert() 是一個邏輯運算。結果是運算元的按位取反。

logical_or(other, context=None)

logical_or() 是一個邏輯運算,它接受兩個*邏輯運算元*(參見 邏輯運算元)。結果是兩個運算元的按位 or

logical_xor(other, context=None)

logical_xor() 是一個邏輯運算,它接受兩個*邏輯運算元*(參見 邏輯運算元)。結果是兩個運算元的按位異或。

max(other, context=None)

類似於 max(self, other),但在返回前會應用上下文的舍入規則,並且 NaN 值會被髮訊號或忽略(取決於上下文以及它們是信令 NaN 還是靜默 NaN)。

max_mag(other, context=None)

max() 方法類似,但比較是使用運算元的絕對值進行的。

min(other, context=None)

類似於 min(self, other),但在返回前會應用上下文的舍入規則,並且 NaN 值會被髮訊號或忽略(取決於上下文以及它們是信令 NaN 還是靜默 NaN)。

min_mag(other, context=None)

min() 方法類似,但比較是使用運算元的絕對值進行的。

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 (非數字)。

  • "sNaN",表示運算元是一個信令 NaN。

quantize(exp, rounding=None, context=None)

返回一個等於第一個運算元經過舍入後的值,且該值具有第二個運算元的指數。

>>> Decimal('1.41421356').quantize(Decimal('1.000'))
Decimal('1.414')

與其他操作不同,如果 quantize 操作後係數的長度會大於精度,則會發出 InvalidOperation 訊號。這保證了除非出現錯誤條件,否則量化後的指數總是等於右側運算元的指數。

同樣與其他操作不同,quantize 永遠不會發出 Underflow 訊號,即使結果是次正規且不精確的。

如果第二個運算元的指數大於第一個運算元的指數,則可能需要進行舍入。在這種情況下,舍入模式由 rounding 引數(如果給出)確定,否則由給定的 context 引數確定;如果兩個引數都未給出,則使用當前執行緒上下文的舍入模式。

當結果指數大於 Emax 或小於 Etiny() 時,會返回錯誤。

radix()

返回 Decimal(10),即 Decimal 類執行所有算術運算的基數。為與規範相容而包含。

remainder_near(other, context=None)

返回用 otherself 的餘數。這與 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 之間的整數。第二個運算元的絕對值給出要旋轉的位置數。如果第二個運算元是正數,則向左旋轉;否則向右旋轉。第一個運算元的係數在必要時會在左側用零填充至長度為 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)

舍入到最近的整數,如果發生舍入,則酌情發出 InexactRounded 訊號。舍入模式由 rounding 引數(如果給出)確定,否則由給定的 context 確定。如果兩個引數都未給出,則使用當前上下文的舍入模式。

to_integral_value(rounding=None, context=None)

舍入到最近的整數,不發出 InexactRounded 訊號。如果給出,則應用 rounding;否則,使用提供的 context 或當前上下文中的舍入方法。

Decimal 數可以使用 round() 函式進行舍入。

round(number)
round(number, ndigits)

如果 ndigits 未給出或為 None,則返回最接近 numberint,平局時舍入到偶數,並忽略 Decimal 上下文的舍入模式。如果 number 是無窮大,則引發 OverflowError;如果是(靜默或信令)NaN,則引發 ValueError

如果 ndigits 是一個 int,則會遵循上下文的舍入模式,並返回一個 Decimal,表示 number 舍入到最接近 Decimal('1E-ndigits') 的倍數;在這種情況下,round(number, ndigits) 等價於 self.quantize(Decimal('1E-ndigits'))。如果 number 是靜默 NaN,則返回 Decimal('NaN')。如果 number 是無窮大、信令 NaN,或者如果量化操作後係數的長度會大於當前上下文的精度,則引發 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'))     # context rounding ignored
4
>>> round(Decimal('3.5'))      # round-ties-to-even
4
>>> round(Decimal('3.75'), 0)  # uses the context rounding
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() 方法期望它們的引數是*邏輯運算元*。一個*邏輯運算元*是一個 Decimal 例項,其指數和符號都為零,並且其所有數字都是 01

上下文物件

上下文是算術運算的環境。它們控制精度、設定舍入規則、確定哪些訊號被視為異常,並限制指數的範圍。

每個執行緒都有自己的當前上下文,可以使用 getcontext()setcontext() 函式訪問或更改。

decimal.getcontext()

返回活動執行緒的當前上下文。

decimal.setcontext(c, /)

將活動執行緒的當前上下文設定為 c

你還可以使用 with 語句和 localcontext() 函式來臨時更改活動上下文。

decimal.localcontext(ctx=None, **kwargs)

返回一個上下文管理器,它將在進入 with 語句時將活動執行緒的當前上下文設定為 ctx 的一個副本,並在退出 with 語句時恢復之前的上下文。如果未指定上下文,則使用當前上下文的副本。kwargs 引數用於設定新上下文的屬性。

例如,以下程式碼將當前十進位制精度設定為 42 位,執行一次計算,然後自動恢復之前的上下文。

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

使用關鍵字引數,程式碼將是以下形式:

from decimal import localcontext

with localcontext(prec=42) as ctx:
    s = calculate_something()
s = +s

如果 kwargs 提供了 Context 不支援的屬性,則引發 TypeError。如果 kwargs 為某個屬性提供了無效的值,則引發 TypeErrorValueError

在 3.11 版更改: localcontext() 現在支援透過使用關鍵字引數來設定上下文屬性。

decimal.IEEEContext(bits)

返回一個上下文物件,該物件被初始化為某個 IEEE 交換格式的適當值。引數必須是 32 的倍數並且小於 IEEE_CONTEXT_MAX_BITS

在 3.14 版本加入。

新的上下文也可以使用下面描述的 Context 建構函式來建立。此外,該模組提供了三個預製的上下文

decimal.BasicContext

這是由通用十進位制算術規範定義的標準上下文。精度設定為 9。舍入設定為 ROUND_HALF_UP。所有標誌都被清除。除了 InexactRoundedSubnormal 之外,所有陷阱都被啟用(視為異常)。

因為啟用了許多陷阱,這個上下文對於除錯很有用。

decimal.ExtendedContext

這是由通用十進位制算術規範定義的標準上下文。精度設定為 9。舍入設定為 ROUND_HALF_EVEN。所有標誌都被清除。沒有啟用任何陷阱(因此在計算期間不會引發異常)。

由於陷阱被停用,這個上下文對於那些傾向於得到 NaNInfinity 結果值而不是引發異常的應用程式很有用。這允許應用程式在存在本會中止程式的條件下完成執行。

decimal.DefaultContext

此上下文被 Context 建構函式用作新上下文的原型。更改一個欄位(例如精度)會影響由 Context 建構函式建立的新上下文的預設值。

這個上下文在多執行緒環境中最有用。在啟動執行緒之前更改其中一個欄位,可以起到設定系統級預設值的效果。不建議線上程啟動後更改欄位,因為這需要執行緒同步以防止競爭條件。

在單執行緒環境中,最好完全不使用此上下文。而是如下所述顯式地建立上下文。

預設值為 Context.prec=28Context.rounding=ROUND_HALF_EVEN,併為 OverflowInvalidOperationDivisionByZero 啟用陷阱。

除了三個提供的上下文之外,還可以使用 Context 建構函式建立新的上下文。

class decimal.Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None)

建立一個新的上下文。如果一個欄位未指定或是 None,則從 DefaultContext 複製預設值。如果 flags 欄位未指定或是 None,則所有標誌都被清除。

prec

一個在 [1, MAX_PREC] 範圍內的整數,用於設定上下文中算術運算的精度。

rounding

列在 舍入模式 部分的常量之一。

traps
flags

要設定的任何訊號的列表。通常,新的上下文應該只設置陷阱並保持標誌清除。

Emin
Emax

指定指數允許的外部限制的整數。Emin 必須在 [MIN_EMIN, 0] 範圍內,Emax 必須在 [0, MAX_EMAX] 範圍內。

capitals

值為 01(預設值)。如果設定為 1,指數將用大寫字母 E 列印;否則,將使用小寫字母 eDecimal('6.02e+23')

clamp

值為 0(預設值)或 1。如果設定為 1,在此上下文中可表示的 Decimal 例項的指數 e 將嚴格限制在 Emin - prec + 1 <= e <= Emax - prec + 1 範圍內。如果 clamp0,則適用一個較弱的條件:Decimal 例項的調整後指數最多為 Emax。當 clamp1 時,一個大的正規數在可能的情況下,其指數會被減小,並在其係數上新增相應數量的零,以適應指數約束;這保留了數字的值,但會丟失關於有效尾隨零的資訊。例如

>>> Context(prec=6, Emax=999, clamp=1).create_decimal('1.23e999')
Decimal('1.23000E+999')

clamp 值為 1 允許與 IEEE 754 中指定的固定寬度十進位制交換格式相容。

Context 類定義了幾個通用方法,以及大量用於在給定上下文中直接進行算術運算的方法。此外,對於上面描述的每個 Decimal 方法(除了 adjusted()as_tuple() 方法),都有一個對應的 Context 方法。例如,對於一個 Context 例項 C 和一個 Decimal 例項 xC.exp(x) 等同於 x.exp(context=C)。每個 Context 方法在接受 Decimal 例項的任何地方也接受一個 Python 整數(int 的例項)。

clear_flags()

將所有標誌重置為 0

clear_traps()

將所有陷阱重置為 0

在 3.3 版本加入。

copy()

返回上下文的副本。

copy_decimal(num, /)

返回 Decimal 例項 num 的副本。

create_decimal(num='0', /)

num 建立一個新的 Decimal 例項,但使用 self 作為上下文。與 Decimal 建構函式不同,上下文的精度、舍入方法、標誌和陷阱都會應用於轉換。

這很有用,因為常量給出的精度通常比應用程式需要的更高。另一個好處是,舍入會立即消除超出當前精度的數字所帶來的意外影響。在以下示例中,使用未舍入的輸入意味著向總和中新增零可能會改變結果

>>> 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 作為上下文進行舍入。與 Decimal.from_float() 類方法不同,上下文的精度、舍入方法、標誌和陷阱都會應用於轉換。

>>> 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 版本加入。

Etiny()

返回一個等於 Emin - prec + 1 的值,這是次正規結果的最小指數值。當發生下溢時,指數被設定為 Etiny

Etop()

返回一個等於 Emax - prec + 1 的值。

處理小數的通常方法是建立 Decimal 例項,然後應用算術運算,這些運算在活動執行緒的當前上下文中進行。另一種方法是使用上下文方法在特定上下文中進行計算。這些方法與 Decimal 類的方法類似,這裡只作簡要介紹。

abs(x, /)

返回 x 的絕對值。

add(x, y, /)

返回 xy 的和。

canonical(x, /)

返回相同的 Decimal 物件 x

compare(x, y, /)

數值上比較 xy

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 是一個正規數,則返回 True;否則返回 False

is_qnan(x, /)

如果 x 是一個靜默 NaN,則返回 True;否則返回 False

is_signed(x, /)

如果 x 是負數,則返回 True;否則返回 False

is_snan(x, /)

如果 x 是一個訊號 NaN,則返回 True;否則返回 False

is_subnormal(x, /)

如果 x 是次正規數,則返回 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, /)

返回 xy 的乘積。

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 中的一元字首加號運算子。此操作會應用上下文精度和舍入,因此它*不是*一個恆等操作。

power(x, y, modulo=None)

返回 xy 次冪,如果給定 modulo,則對其取模。

使用兩個引數,計算 x**y。如果 x 是負數,則 y 必須是整數。除非 y 是整數並且結果是有限的且可以用 'precision' 位數精確表示,否則結果將是不精確的。使用上下文的舍入模式。在 Python 版本中,結果總是正確舍入的。

Decimal(0) ** Decimal(0) 會導致 InvalidOperation,如果 InvalidOperation 未被捕獲,則結果為 Decimal('NaN')

在 3.3 版更改: C 模組根據正確舍入的 exp()ln() 函式計算 power()。結果是明確定義的,但只是“幾乎總是正確舍入”。

使用三個引數,計算 (x**y) % modulo。對於三個引數的形式,以下對引數的限制適用

  • 所有三個引數必須是整數

  • y 必須為非負數

  • xy 中至少有一個必須為非零

  • modulo 必須為非零,且最多有 'precision' 位數

Context.power(x, y, modulo) 的結果值等於使用無限精度計算 (x**y) % modulo 所獲得的值,但計算效率更高。結果的指數為零,無論 xymodulo 的指數如何。結果總是精確的。

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, /)

返回第一個運算元,其指數加上第二個值。

shift(x, y, /)

返回 x 移位 y 次後的副本。

sqrt(x, /)

非負數的平方根,精度為上下文精度。

subtract(x, y, /)

返回 xy 之間的差。

to_eng_string(x, /)

轉換為字串,如果需要指數,則使用工程記數法。

工程記數法的指數是 3 的倍數。這可能在小數點左側留下最多 3 位數字,並可能需要新增一個或兩個尾隨的零。

to_integral_exact(x, /)

舍入為整數。

to_sci_string(x, /)

使用科學記數法將數字轉換為字串。

常量

本節中的常量僅與 C 模組相關。為了相容性,它們也包含在純 Python 版本中。

32 位

64 位

decimal.MAX_PREC

425000000

999999999999999999

decimal.MAX_EMAX

425000000

999999999999999999

decimal.MIN_EMIN

-425000000

-999999999999999999

decimal.MIN_ETINY

-849999999

-1999999999999999997

decimal.IEEE_CONTEXT_MAX_BITS

256

512

decimal.HAVE_THREADS

值為 True。已棄用,因為 Python 現在總是支援執行緒。

自 3.9 版本起棄用。

decimal.HAVE_CONTEXTVAR

預設值為 True。如果 Python 使用 --without-decimal-contextvar 選項配置,C 版本將使用執行緒區域性上下文而不是協程區域性上下文,此時值為 False。在某些巢狀上下文場景中,這樣做會稍快一些。

在 3.8.3 版中新增。

舍入模式

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.Clamped

改變了一個指數以適應表示約束。

通常,當指數超出上下文的 EminEmax 限制時,會發生鉗位。如果可能,透過向係數新增零來減小指數以適應範圍。

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。在任何一種情況下,InexactRounded 也會被觸發。

class decimal.Rounded

發生了舍入,儘管可能沒有資訊丟失。

每當舍入丟棄數字時都會發出訊號;即使這些數字是零(例如將 5.00 舍入為 5.0)。如果未被捕獲,則返回結果不變。此訊號用於檢測有效數字的丟失。

class decimal.Subnormal

舍入前的指數低於 Emin

當操作結果是次正規數(指數太小)時發生。如果未被捕獲,則返回結果不變。

class decimal.Underflow

數值下溢,結果舍入為零。

當一個次正規結果因舍入而被推到零時發生。InexactSubnormal 也會被觸發。

class decimal.FloatOperation

為混合浮點數和 Decimals 啟用更嚴格的語義。

如果訊號未被捕獲(預設情況),則在 Decimal 建構函式、create_decimal() 和所有比較運算子中允許混合浮點數和 Decimals。轉換和比較都是精確的。任何混合操作的發生都會透過在上下文標誌中設定 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 提供了兩個有啟發性的例子,其中精度不足的舍入浮點運算導致加法的結合律和分配律失效

# Examples from Seminumerical Algorithms, Section 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 模組的數字系統提供了特殊值,包括 NaNsNaN-InfinityInfinity 和兩個零,+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() 將自動為當前執行緒建立一個新的上下文。新的上下文物件的預設值從 decimal.DefaultContext 物件設定。

sys.flags.thread_inherit_context 標誌會影響新執行緒的上下文。如果該標誌為 false,新執行緒將以空上下文啟動。在這種情況下,getcontext() 在被呼叫時會建立一個新的上下文物件,並使用來自 DefaultContext 的預設值。如果該標誌為 true,新執行緒將以 threading.Thread.start() 呼叫者的上下文副本啟動。

為了控制預設值,以便每個執行緒在整個應用程式中使用相同的值,可以直接修改 DefaultContext 物件。這應該在任何執行緒啟動之前完成,這樣就不會在呼叫 getcontext() 的執行緒之間出現競爭條件。例如:

# Set applicationwide defaults for all threads about to be launched
DefaultContext.prec = 12
DefaultContext.rounding = ROUND_DOWN
DefaultContext.traps = ExtendedContext.traps.copy()
DefaultContext.traps[InvalidOperation] = 1
setcontext(DefaultContext)

# Afterwards, the threads can be started
t1.start()
t2.start()
t3.start()
 . . .

範例

以下是一些作為實用函式並演示如何使用 Decimal 類的示例:

def moneyfmt(value, places=2, curr='', sep=',', dp='.',
             pos='', neg='-', trailneg=''):
    """Convert Decimal to a money formatted string.

    places:  required number of places after the decimal point
    curr:    optional currency symbol before the sign (may be blank)
    sep:     optional grouping separator (comma, period, space, or blank)
    dp:      decimal point indicator (comma or period)
             only specify as blank when places is zero
    pos:     optional sign for positive numbers: '+', space or blank
    neg:     optional sign for negative numbers: '-', '(', space or blank
    trailneg:optional trailing minus indicator:  '-', ')', space or blank

    >>> 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 places --> '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():
    """Compute Pi to the current precision.

    >>> print(pi())
    3.141592653589793238462643383

    """
    getcontext().prec += 2  # extra digits for intermediate steps
    three = Decimal(3)      # substitute "three=3.0" for regular floats
    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               # unary plus applies the new precision

def exp(x):
    """Return e raised to the power of x.  Result type matches input type.

    >>> 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):
    """Return the cosine of x as measured in radians.

    The Taylor series approximation works best for a small value of x.
    For larger values, first compute 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):
    """Return the sine of x as measured in radians.

    The Taylor series approximation works best for a small value of x.
    For larger values, first compute 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')

問:有很多方法可以表示相同的值。數字 200200.0002E2.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')]

問:計算中何時發生四捨五入?

答:它發生在計算之後。十進位制規範的理念是,數字被認為是精確的,並且獨立於當前上下文建立。它們甚至可以具有比當前上下文更高的精度。計算使用這些精確的輸入進行處理,然後將四捨五入(或其他上下文操作)應用於計算的結果

>>> getcontext().prec = 5
>>> pi = Decimal('3.1415926535')   # More than 5 digits
>>> pi                             # All digits are retained
Decimal('3.1415926535')
>>> pi + 0                         # Rounded after an addition
Decimal('3.1416')
>>> pi - Decimal('0.00005')        # Subtract unrounded numbers, then round
Decimal('3.1415')
>>> pi + 0 - Decimal('0.00005').   # Intermediate values are rounded
Decimal('3.1416')

問:有些十進位制值總是以指數表示法列印。有沒有辦法得到非指數表示法?

答:對於某些值,指數表示法是表示係數中有效位數的唯一方法。例如,將 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')

問:有沒有辦法將常規浮點數轉換為 Decimal

答:是的,任何二進位制浮點數都可以精確地表示為 Decimal,儘管精確轉換可能需要比直覺上認為的更高的精度。

>>> Decimal(math.pi)
Decimal('3.141592653589793115997963468544185161590576171875')

問:在一個複雜的計算中,我如何確保我沒有因為精度不足或四捨五入異常而得到虛假結果?

答:decimal 模組使得測試結果變得容易。最佳實踐是使用更高的精度和不同的四捨五入模式重新執行計算。結果差異很大表明精度不足、四捨五入模式問題、輸入條件不佳或演算法數值不穩定。

問:我注意到上下文精度應用於操作的結果,但不應用於輸入。混合不同精度的值時有什麼需要注意的嗎?

答:是的。原則是所有值都被認為是精確的,對這些值的算術也是精確的。只有結果會被四捨五入。對輸入的好處是“所輸即所得”。缺點是,如果你忘記了輸入沒有被四捨五入,結果可能會看起來很奇怪。

>>> 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')      # unary plus triggers rounding
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 乘法,對非常大的數使用 數論變換

上下文必須適應精確的任意精度算術。EminEmax 應始終設定為最大值,clamp 應始終為 0(預設值)。設定 prec 需要一些注意。

嘗試大數算術的最簡單方法是也為 prec 使用最大值 [2]

>>> setcontext(Context(prec=MAX_PREC, Emax=MAX_EMAX, Emin=MIN_EMIN))
>>> x = Decimal(2) ** 256
>>> x / 128
Decimal('904625697166532776746648320380374280103671755200316906558262375061821325312')

對於不精確的結果,在 64 位平臺上 MAX_PREC 太大了,可用記憶體會不足。

>>> Decimal(1) / 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError

在有超額分配的系統(例如 Linux)上,更復雜的方法是根據可用 RAM 的量來調整 prec。假設你有 8GB 的 RAM,並預計有 10 個同時操作的運算元,每個最多使用 500MB:

>>> import sys
>>>
>>> # Maximum number of digits for a single operand using 500MB in 8-byte words
>>> # with 19 digits per word (4-byte and 9 digits for the 32-bit build):
>>> maxdigits = 19 * ((500 * 1024**2) // 8)
>>>
>>> # Check that this works:
>>> c = Context(prec=maxdigits, Emax=MAX_EMAX, Emin=MIN_EMIN)
>>> c.traps[Inexact] = True
>>> setcontext(c)
>>>
>>> # Fill the available precision with nines:
>>> 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 陷阱。