decimal--- 十进制定点和浮点运算¶

👁️ 9587 ❤️ 94
decimal--- 十进制定点和浮点运算¶

Decimal 常见问题¶

问:输入 decimal.Decimal('1234.5') 很麻烦。在使用交互式解释器时,有没有办法减少输入?

答:一些用户将构造函数缩写为一个字母:

>>> D = decimal.Decimal

>>> D('1.23') + D('3.45')

Decimal('4.68')

问:在一个有两位小数的定点应用中,一些输入有很多位小数需要四舍五入。另一些则不应该有多余的数字,需要进行验证。应该使用什么方法?

答:quantize() 方法可以四舍五入到固定的小数位数。如果设置了 Inexact 陷阱,它也对验证很有用:

>>> TWOPLACES = Decimal(10) ** -2 # same as Decimal('0.01')

>>> # Round to two places

>>> Decimal('3.214').quantize(TWOPLACES)

Decimal('3.21')

>>> # Validate that a number does not exceed two places

>>> Decimal('3.21').quantize(TWOPLACES, context=Context(traps=[Inexact]))

Decimal('3.21')

>>> Decimal('3.214').quantize(TWOPLACES, context=Context(traps=[Inexact]))

Traceback (most recent call last):

...

Inexact: None

问:一旦我有了有效的两位小数输入,如何在整个应用程序中保持这种不变性?

答:一些操作,如加法、减法和与整数相乘,会自动保留定点。其他操作,如除法和非整数乘法,会改变小数位数,需要后续使用 quantize() 步骤。

>>> a = Decimal('102.72') # Initial fixed-point values

>>> b = Decimal('3.17')

>>> a + b # Addition preserves fixed-point

Decimal('105.89')

>>> a - b

Decimal('99.55')

>>> a * 42 # So does integer multiplication

Decimal('4314.24')

>>> (a * b).quantize(TWOPLACES) # Must quantize non-integer multiplication

Decimal('325.62')

>>> (b / a).quantize(TWOPLACES) # And quantize division

Decimal('0.03')

在开发定点应用程序时,定义函数来处理 quantize() 步骤会很方便:

>>> def mul(x, y, fp=TWOPLACES):

... return (x * y).quantize(fp)

...

>>> def div(x, y, fp=TWOPLACES):

... return (x / y).quantize(fp)

>>> mul(a, b) # Automatically preserve fixed-point

Decimal('325.62')

>>> div(b, a)

Decimal('0.03')

问:有很多方法可以表示相同的值。数字 200、200.000、2E2 和 .02E+4 在不同精度下都具有相同的值。有没有办法将它们转换为一个可识别的规范值?

答:normalize() 方法将所有等效值映射到一个单一的代表值:

>>> values = map(Decimal, '200 200.000 2E2 .02E+4'.split())

>>> [v.normalize() for v in values]

[Decimal('2E+2'), Decimal('2E+2'), Decimal('2E+2'), Decimal('2E+2')]

问:计算中何时发生四舍五入?

答:它发生在计算之后。十进制规范的理念是,数字被认为是精确的,并且独立于当前上下文创建。它们甚至可以具有比当前上下文更高的精度。计算使用这些精确的输入进行处理,然后将四舍五入(或其他上下文操作)应用于计算的结果。

>>> 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 乘法,对非常大的数使用 数论变换。

上下文必须适应精确的任意精度算术。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 陷阱。

[1]

在 3.3 版本加入。

[2]

在 3.9 版更改: 此方法现在适用于除整数次幂以外的所有精确结果。

← 花园宝宝中文版全集(2013) 日本熊出沒全國分佈圖!多個港人旅遊熱門地都有 只有3區最安全 →