Decimal 常见问题¶
Q. 总是输入 decimal.Decimal('1234.5') 是否过于笨拙。 在使用交互解释器时有没有最小化输入量的方式?
A. 有些用户会将构造器简写为一个字母:
>>>D = decimal.Decimal
>>>D('1.23') + D('3.45')
Decimal('4.68')
Q. 在带有两个十进制位的定点数应用中,有些输入值具有许多位,需要被舍入。 另一些数则不应具有多余位,需要验证有效性。 这种情况应该用什么方法?
A. 用 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
Q. 当我使用两个有效位的输入时,我要如何在一个应用中保持有效位不变?
A. 某些运算例如与整数相加、相减和相乘将会自动保留固定的小数位数。 其他运算,例如相除和非整数相乘则将会改变小数位数,需要再加上 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')
Q. 表示同一个值有许多方式。 数字 200, 200.000, 2E2 和 02E+4 的值都相同但有精度不同。 是否有办法将它们转换为一个可识别的规范值?
A. 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')]
Q. 有些十进制值总是被打印为指数表示形式。 是否有办法得到一个非指数表示形式?
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. 是否有办法将一个普通浮点数转换为 Decimal?
A. 是的,任何二进制浮点数都可以精确地表示为 Decimal 值,但完全精确的转换可能需要比平常感觉更高的精度:
>>>Decimal(math.pi)
Decimal('3.141592653589793115997963468544185161590576171875')
Q. 在一个复杂的计算中,我怎样才能保证不会得到由精度不足和舍入异常所导致的虚假结果。
A. 使用 decimal 模块可以很容易地检测结果。 最好的做法是使用更高的精度和不同的舍入模式重新进行计算。 明显不同的结果表明存在精度不足、舍入模式问题、不符合条件的输入或是结果不稳定的算法。
Q. 我发现上下文精度的应用只针对运算结果而不针对输入。在混合使用不同精度的值时有什么需要注意的吗?
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') # unary plus triggers rounding
Decimal('1.23')
>>>Context(prec=5, rounding=ROUND_DOWN).create_decimal('1.2345678')
Decimal('1.2345')
Q. CPython 实现对于
巨大数字是否足够快速?
A. 是的。 在 CPython 和 PyPy3 实现中,decimal 模块的 C/CFFI 版本集成了高速 libmpdec 库用于实现任意精度正确舍入的十进制浮点算术 1。 libmpdec 会对中等大小的数字使用 Karatsuba 乘法 而对非常巨大的数字使用 数字原理变换。
必须要对任意精度算术适配上下文。 Emin 和 Emax 应当总是设为最大值,clamp 应当总是设为 0 (默认值)。 设置 prec 需要十分谨慎。
进行大数字算术的最便捷方式也是使用 prec 的最大值 2:
>>>setcontext(Context(prec=MAX_PREC, Emax=MAX_EMAX, Emin=MIN_EMIN))
>>>x = Decimal(2) ** 256
>>>x / 128
Decimal('904625697166532776746648320380374280103671755200316906558262375061821325312')
对于不精确的结果,在 64 位平台上 MAX_PREC 的值太大了,可用的内存将会不足:
>>>Decimal(1) / 3
Traceback (most recent call last):
File "", line 1, in
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 "", line 1, in
decimal.Inexact: []
总体而言(特别是在没有超量分配的系统上),如果期望所有计算都是精确的则推荐预估更严格的边界并设置 Inexact 陷阱。
3.3 新版功能.
在 3.9 版更改:此方式现在适用于除了非整数乘方以外的所有精确结果。