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 陷阱。