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 數字、算術情境和訊號。

decimal 數字是不可變的。它有符號、係數數字和指數。為了保持有效性,係數數字不會截斷尾隨零。Decimal 也包含特殊值,如 Infinity-InfinityNaN。標準也區分 -0+0

算術情境是一個指定精度、捨入規則、指數限制、指示運算結果的旗標,以及決定訊號是否被視為例外的陷阱啟用器的環境。捨入選項包括 ROUND_CEILINGROUND_DOWNROUND_FLOORROUND_HALF_DOWNROUND_HALF_EVENROUND_HALF_UPROUND_UPROUND_05UP

訊號是在計算過程中產生的例外條件群組。根據應用程式的需求,訊號可能被忽略、視為資訊性質,或被視為例外。decimal 模組中的訊號包括:ClampedInvalidOperationDivisionByZeroInexactRoundedSubnormalOverflowUnderflowFloatOperation

每個訊號都有一個旗標和一個陷阱啟用器。當遇到訊號時,其旗標被設定為一,然後,如果陷阱啟用器被設定為一,就會引發例外。旗標是黏性的,因此使用者需要在監控計算之前重設它們。

也參考

快速入門教學

使用 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 實例可以從整數、字串、浮點數或元組建構。從整數或浮點數建構會執行該整數或浮點數值的精確轉換。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 也提供一些數學函式:

>>> 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 條目顯示對 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'。大小寫不重要,所以例如 infInfINFINITYiNfINity 都是正無窮大的可接受拼寫。

如果 valuetuple,它應該有三個元件:一個符號(0 代表正數或 1 代表負數)、一個數字的 tuple,以及一個整數指數。例如,Decimal((0, (1, 4, 1, 4), -3)) 回傳 Decimal('1.414')

如果 valuefloat,二進位浮點數值會被無損轉換為其精確的十進位等價值。這種轉換通常需要 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 浮點數物件與其他內建數值型別(如 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 實例結合:例如,嘗試將 Decimalfloat 相加會引發 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)

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 也包含在全序中。如果兩個運算元具有相同的表示,此函式的結果是 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 版被加入.

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() 是一個邏輯運算,接受兩個 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 值會被發出訊號或忽略(取決於情境以及它們是發出訊號或安靜的)。

max_mag(other, context=None)

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

min(other, context=None)

類似於 min(self, other),但在回傳前會套用情境捨入規則,且 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)

回傳 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)

捨入到最近的整數,如果發生捨入則適當地發出 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

如果 ndigitsint,會遵守情境的捨入模式,並回傳代表 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 operandslogical operand 是指數和符號都為零,且數字全部為 01Decimal 實例。

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 為屬性提供了無效值,則引發 TypeErrorValueError

在 3.11 版的變更: localcontext() 現在支援透過關鍵字引數來設定 context 屬性。

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

decimal.BasicContext

這是由通用十進位算術規格定義的標準 context。精度設定為九。將位設定為 ROUND_HALF_UP。所有標誌都被清除。所有的 trap 都被啟用(視為例外),除了 InexactRoundedSubnormal

因為很多 trap 都被啟用,這個 context 對於除錯很有用。

decimal.ExtendedContext

這是由通用十進位算術規格定義的標準 context。精度設定為九。將位設定為 ROUND_HALF_EVEN。所有標誌都被清除。沒有 trap 被啟用(因此在運算過程中不會引發例外)。

因為 trap 被停用,這個 context 對於傾向於拿到 NaNInfinity 結果值而不是引發例外的應用程式很有用。這讓應用程式在存在原本會中止程式的條件時也能完成執行。

decimal.DefaultContext

這個 context 被 Context 建構子用作新 context 的原型。改變一個字段(例如精度)的效果是改變 Context 建構子建立的新 context 的預設值。

這個 context 在多執行緒環境中最有用。在執行緒啟動之前改變其中一個字段的效果是設定系統級預設值。不建議在執行緒啟動後改變字段,因為這會需要執行緒同步來防止競爵條件。

在單執行緒環境中,最好完全不使用這個 context。取而代之,只需按下面描述的方式明確建立 context。

預設值為 Context.prec=28Context.rounding=ROUND_HALF_EVEN,並啟用 OverflowInvalidOperationDivisionByZero 的 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,則清除所有標誌。

prec

在 [1, MAX_PREC] 範圍內的整數,用於設定 context 中算術運算的精度。

rounding

Rounding Modes 段落中列出的常數之一。

traps
flags

要設定的任何信號列表。一般來說,新 context 應該只設定 trap 並保持標誌清除。

Emin
Emax

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

capitals

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

clamp

0``(預設)或 ``1。如果設定為 1,在此 context 中可表示的 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 類別定義了幾個通用方法,以及大量在給定 context 中直接進行算術運算的方法。此外,對於上面描述的每個 Decimal 方法(除了 adjusted()as_tuple() 方法之外),都有相對應的 Context 方法。例如,對於 Context 實例 CDecimal 實例 xC.exp(x) 等價於 x.exp(context=C)。每個 Context 方法在接受 Decimal 實例的任何地方都接受 Python 整數(int 的實例)。

clear_flags()

將所有標誌重設為 0

clear_traps()

將所有 trap 重設為 0

在 3.3 版被加入.

copy()

返回 context 的一個副本。

copy_decimal(num)

返回 Decimal 實例 num 的副本。

create_decimal(num)

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 版被加入.

Etiny()

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

Etop()

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

處理 decimal 的通常方法是建立 Decimal 實例,然後在活動執行緒的當前 context 中進行算術運算。另一種方法是使用 context 方法在特定context 中進行計算。這些方法與 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 的十進位對數。

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 中的一元前綴加號運算子。此運算會套用 context 精度和將位,因此它 不是 一個恆等運算。

power(x, y, modulo=None)

返回 xy 次方,如果有提供則總是 modulo 的簡化結果。

使用兩個引數計算 x**y。如果 x 是負數,則 y 必須是整數。除非 y 是整數且結果是有限的且可以在「精度」位數中精確表示,否則結果將是不精確的。使用 context 的將位模式。在 Python 版本中,結果總是正確將位的。

Decimal(0) ** Decimal(0) 會導致 InvalidOperation,如果 InvalidOperation 沒有被捕獲,則會產生 Decimal('NaN')

在 3.3 版的變更: C 模組以正確將位的 exp()ln() 函式來計算 power()。結果是定義良好的,但只是「幾乎總是正確將位」。

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

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

  • y 必須是非負數

  • xy 中至少一個必須是非零

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

返回第一個運算元在加上第二個值的 exp 後的結果。

shift(x, y)

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

sqrt(x)

非負數在 context 精度下的平方根。

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

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

在 3.9 版之後被棄用.

decimal.HAVE_CONTEXTVAR

預設值是 True。如果 Python 是使用 --without-decimal-contextvar 選項 配置的,C 版本使用執行緒區域而非協程區域 context,且值為 False。在某些巢狀 context 情境中這稍微快一些。

在 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

啟用混合 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 模組的數字系統提供特殊值,包括 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() 會自動建立一個新的情境供目前執行緒使用。

新的 context 是從稱為 DefaultContext 的原型 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')

問:有很多方式表達相同的值。數字 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')]

問:在計算中什麼時候發生捨入?

答:它發生在計算 之後。decimal 規範的理念是數字被視為精確的,並且獨立於目前情境而建立。它們甚至可以比目前情境具有更高的精度。計算使用這些精確的輸入進行處理,然後將捨入(或其他情境運算)應用於計算的 結果

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

Q. 某些 decimal 值總是以指數記號列印。有辦法取得非指數表示法嗎?

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

Q. 有辦法將一般 float 轉換為 Decimal 嗎?

A. 是的,任何二進位浮點數都可以精確表示為 Decimal,儘管精確轉換可能需要比直覺所暗示的更高精度:

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

Q. 在複雜計算中,我如何確保沒有因為精度不足或將位異常而得到虛假結果。

A. decimal 模組使得測試結果很容易。最佳做法是使用更高精度和各種將位模式重新執行計算。差異很大的結果表示精度不足、將位模式問題、情況不佳的輸入或數值不穩定的演算法。

Q. 我注意到 context 精度會套用於運算結果但不套用於輸入。在混合不同精度的值時有什麼需要注意的嗎?

A. 是的。原則是所有值都被認為是精確的,對這些值的算術運算也是如此。只有結果會被四捨五入。輸入的優點是「你輸入的就是你得到的」。缺點是如果你忘記輸入沒有被四捨五入,結果可能看起來很奇怪:

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

Q. CPython 實作對於大數字快速嗎?

A. 是的。在 CPython 和 PyPy3 實作中,decimal 模組的 C/CFFI 版本整合了高速`libmpdec <https://www.bytereef.org/mpdecimal/doc/libmpdec/index.html>`_ 程式庫,用於任意精度正確將位的十進位浮點運算 [1]libmpdec 對中等大小的數字使用 Karatsuba 乘法,對於非常大的數字使用 數論變換

context 必須適應精確的任意精度運算。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')

對於不精確的結果,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
>>>
>>> # 單一運算元使用 500MB 8 位元組字元的最大位數
>>> # 每個字元 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,如果所有計算都預期是精確的。