6.2. decimal — 高精度计算模块

未匹配的标注

目的:使用定点数和浮点数进行十进制计算

高精度计算模块 decimal 采用了大多数人熟悉的浮点和定点数实现,而不是程序员们熟悉的,同时也是大多数计算机硬件支持的IEEE版本的浮点数实现。 一个 Decimal 对象能够精确地表示任何数字,以及能够无条件进位或无条件舍去到任意的精度。

十进制值

 Decimal 类的实例表示为十进制的值。构造函数采用一个整数或字符串作为参数。在创建 Decimal对象之前,可以将浮点数转换为字符串,从而让调用者显式地处理不能用硬件浮点数表示的数位。或者,使用类方法 from_float() 精确地转换为十进制表示形式。

decimal_create.py

import decimal

fmt = '{0:<25} {1:<25}'

print(fmt.format('Input', 'Output'))
print(fmt.format('-' * 25, '-' * 25))

# 整数
print(fmt.format(5, decimal.Decimal(5)))

# 字符串
print(fmt.format('3.14', decimal.Decimal('3.14')))

# 浮点数
f = 0.1
print(fmt.format(repr(f), decimal.Decimal(str(f))))
print('{:<0.23g} {:<25}'.format(
    f,
    str(decimal.Decimal.from_float(f))[:25])
)

浮点数 0.1 的值在二进制中并不是一个精确值,所以 float 类型和 Decimal 类型的值不同。在该输出的最后一行中,完整的字符串表示被截断为25个字符。

$ python3 decimal_create.py

Input                     Output
------------------------- -------------------------
5                         5
3.14                      3.14
0.1                       0.1
0.10000000000000000555112 0.10000000000000000555111

Decimals 同样能从一个元组创建,其包含符号位 「0 为正, 1 为负」 , 一个 tuple 类型的数位元组,和一个表示指数的整数。

decimal_tuple.py

import decimal

# 元组
t = (1, (1, 1), -2)
print('Input  :', t)
print('Decimal:', decimal.Decimal(t))
基于元组的表示方式不便于创建,但确实提供了一种在不损失精度的前提下便捷地导出十进制值的方式。元组表单能够通过网络传输或者存储在不支持精确十进制值的数据库中,之后再转换回 Decimal 实例。
$ python3 decimal_tuple.py

Input  : (1, (1, 1), -2)
Decimal: -0.11

格式化

Decimal 使用和其它数值类型一样的语法和选项来响应 Python 的 字符串格式化协议

decimal_format.py

import decimal

d = decimal.Decimal(1.1)
print('Precision:')
print('{:.1}'.format(d))
print('{:.2}'.format(d))
print('{:.3}'.format(d))
print('{:.18}'.format(d))

print('\nWidth and precision combined:')
print('{:5.1f} {:5.1g}'.format(d, d))
print('{:5.2f} {:5.2g}'.format(d, d))
print('{:5.2f} {:5.2g}'.format(d, d))

print('\nZero padding:')
print('{:05.1}'.format(d))
print('{:05.2}'.format(d))
print('{:05.3}'.format(d))

格式字符串可以控制输出的宽度,精度(有效位数)以及如何填充值以填充宽度。

$ python3 decimal_format.py

Precision:
1
1.1
1.10
1.10000000000000009

Width and precision combined:
  1.1     1
 1.10   1.1
 1.10   1.1

Zero padding:
00001
001.1
01.10

算术

Decimal 重载了简单的算术运算符,因此实例的操作方式与内置数值类型的操作方式大致相同。

decimal_operators.py

import decimal

a = decimal.Decimal('5.1')
b = decimal.Decimal('3.14')
c = 4
d = 3.14

print('a     =', repr(a))
print('b     =', repr(b))
print('c     =', repr(c))
print('d     =', repr(d))
print()

print('a + b =', a + b)
print('a - b =', a - b)
print('a * b =', a * b)
print('a / b =', a / b)
print()

print('a + c =', a + c)
print('a - c =', a - c)
print('a * c =', a * c)
print('a / c =', a / c)
print()

print('a + d =', end=' ')
try:
    print(a + d)
except TypeError as e:
    print(e)

Decimal 运算符也接受整数参数,但浮点值必须转换为 Decimal 实例。

$ python3 decimal_operators.py

a     = Decimal('5.1')
b     = Decimal('3.14')
c     = 4
d     = 3.14

a + b = 8.24
a - b = 1.96
a * b = 16.014
a / b = 1.624203821656050955414012739

a + c = 9.1
a - c = 1.1
a * c = 20.4
a / c = 1.275

a + d = unsupported operand type(s) for +: 'decimal.Decimal' and
 'float'

除了基本算术之外,Decimal包括查找基数10和自然对数的方法。 log10()ln() 的返回值是 Decimal 实例,因此可以直接在带有其他值的公式中使用它们。

特殊值

除了预期的数值,“十进制”可以表示几个特殊值,包括无穷大的正值和负值,“不是数字”和零。

decimal_special.py

import decimal

for value in ['Infinity', 'NaN', '0']:
    print(decimal.Decimal(value), decimal.Decimal('-' + value))
print()

# 数学的无穷
print('Infinity + 1:', (decimal.Decimal('Infinity') + 1))
print('-Infinity + 1:', (decimal.Decimal('-Infinity') + 1))

# 打印比较 NaN
print(decimal.Decimal('NaN') == decimal.Decimal('Infinity'))
print(decimal.Decimal('NaN') != decimal.Decimal(1))

添加到无限值会返回另一个无限值。比较 NaN 的相等性总是返回 false,比较不等总是返回 true。与 NaN 排序次序的比较是未定义的,会导致错误。

$ python3 decimal_special.py

Infinity -Infinity
NaN -NaN
0 -0

Infinity + 1: Infinity
-Infinity + 1: -Infinity
False
True

上下文

到目前为止,所有示例都使用了 decimal 模块的默认行为。可以通过使用 context 覆盖这些设置,例如保留的精度,如何执行舍入,错误处理等。上下文可以应用于线程中的所有 Decimal 实例,也可以应用于小代码区域中的本地。

当前上下文

要检索当前的全局上下文,请使用 getcontext

decimal_getcontext.py

import decimal

context = decimal.getcontext()

print('Emax     =', context.Emax)
print('Emin     =', context.Emin)
print('capitals =', context.capitals)
print('prec     =', context.prec)
print('rounding =', context.rounding)
print('flags    =')
for f, v in context.flags.items():
    print('  {}: {}'.format(f, v))
print('traps    =')
for t, v in context.traps.items():
    print('  {}: {}'.format(t, v))

此示例脚本显示 Context 的公共属性。

$ python3 decimal_getcontext.py

Emax     = 999999
Emin     = -999999
capitals = 1
prec     = 28
rounding = ROUND_HALF_EVEN
flags    =
  <class 'decimal.InvalidOperation'>: False
  <class 'decimal.FloatOperation'>: False
  <class 'decimal.DivisionByZero'>: False
  <class 'decimal.Overflow'>: False
  <class 'decimal.Underflow'>: False
  <class 'decimal.Subnormal'>: False
  <class 'decimal.Inexact'>: False
  <class 'decimal.Rounded'>: False
  <class 'decimal.Clamped'>: False
traps    =
  <class 'decimal.InvalidOperation'>: True
  <class 'decimal.FloatOperation'>: False
  <class 'decimal.DivisionByZero'>: True
  <class 'decimal.Overflow'>: True
  <class 'decimal.Underflow'>: False
  <class 'decimal.Subnormal'>: False
  <class 'decimal.Inexact'>: False
  <class 'decimal.Rounded'>: False
  <class 'decimal.Clamped'>: False

精确度

上下文的 prec 属性控制为算术结果创建的新值保持的精度值。如所描述的那样保持字面量。

decimal_precision.py

import decimal

d = decimal.Decimal('0.123456')

for i in range(1, 5):
    decimal.getcontext().prec = i
    print(i, ':', d, d * 1)

要更改精度,请将 1decimal.MAX_PREC 之间的新值直接指定给属性。

$ python3 decimal_precision.py

1 : 0.123456 0.1
2 : 0.123456 0.12
3 : 0.123456 0.123
4 : 0.123456 0.1235

四舍五入

有几种舍入选项可以使值保持在所需的精度范围内。

ROUND_CEILING

始终向正无穷舍入。

ROUND_DOWN

始终向零舍入。

ROUND_FLOOR

始终向负无穷舍入。

ROUND_HALF_DOWN

如果最后一个有效数字大于或等于 5,则从零开始,否则为零。

ROUND_HALF_EVEN

ROUND_HALF_DOWN 类似,但如果值为 5 则检查前一个数字。偶数值会导致结果向下舍入,奇数位会导致结果向上舍入。

ROUND_HALF_UP

ROUND_HALF_DOWN 类似,除非最后一位有效数字是 5,否则该值将从零开始舍入。

ROUND_UP

往零的反方向舍入。

ROUND_05UP

如果最后一位是 05,则舍入为零,否则为零。

decimal_rounding.py

import decimal

context = decimal.getcontext()

ROUNDING_MODES = [
    'ROUND_CEILING',
    'ROUND_DOWN',
    'ROUND_FLOOR',
    'ROUND_HALF_DOWN',
    'ROUND_HALF_EVEN',
    'ROUND_HALF_UP',
    'ROUND_UP',
    'ROUND_05UP',
]

header_fmt = '{:10} ' + ' '.join(['{:^8}'] * 6)

print(header_fmt.format(
    ' ',
    '1/8 (1)', '-1/8 (1)',
    '1/8 (2)', '-1/8 (2)',
    '1/8 (3)', '-1/8 (3)',
))
for rounding_mode in ROUNDING_MODES:
    print('{0:10}'.format(rounding_mode.partition('_')[-1]),
          end=' ')
    for precision in [1, 2, 3]:
        context.prec = precision
        context.rounding = getattr(decimal, rounding_mode)
        value = decimal.Decimal(1) / decimal.Decimal(8)
        print('{0:^8}'.format(value), end=' ')
        value = decimal.Decimal(-1) / decimal.Decimal(8)
        print('{0:^8}'.format(value), end=' ')
    print()

此程序显示使用不同算法将相同值四舍五入到不同精度级别的效果。

$ python3 decimal_rounding.py

           1/8 (1)  -1/8 (1) 1/8 (2)  -1/8 (2) 1/8 (3)  -1/8 (3)
CEILING      0.2      -0.1     0.13    -0.12    0.125    -0.125

DOWN         0.1      -0.1     0.12    -0.12    0.125    -0.125

FLOOR        0.1      -0.2     0.12    -0.13    0.125    -0.125

HALF_DOWN    0.1      -0.1     0.12    -0.12    0.125    -0.125

HALF_EVEN    0.1      -0.1     0.12    -0.12    0.125    -0.125

HALF_UP      0.1      -0.1     0.13    -0.13    0.125    -0.125

UP           0.2      -0.2     0.13    -0.13    0.125    -0.125

05UP         0.1      -0.1     0.12    -0.12    0.125    -0.125

局部上下文

可以使用 with 语句将上下文应用于代码块。

decimal_context_manager.py

import decimal

with decimal.localcontext() as c:
    c.prec = 2
    print('Local precision:', c.prec)
    print('3.14 / 3 =', (decimal.Decimal('3.14') / 3))

print()
print('Default precision:', decimal.getcontext().prec)
print('3.14 / 3 =', (decimal.Decimal('3.14') / 3))

Context 支持 with 使用的上下文管理器 API,因此设置仅适用于代码块。

$ python3 decimal_context_manager.py

Local precision: 2
3.14 / 3 = 1.0

Default precision: 28
3.14 / 3 = 1.046666666666666666666666667

每个实例上下文

上下文也可用于构造 Decimal 实例,然后继承从上下文转换的精度和舍入参数。

decimal_instance_context.py

import decimal

# 设置精度有限的上下文
c = decimal.getcontext().copy()
c.prec = 3

# 创建我们的常数
pi = c.create_decimal('3.1415')

# 常数值四舍五入
print('PI    :', pi)

# 使用全局上下文常量的结果
print('RESULT:', decimal.Decimal('2.01') * pi)

例如,这使应用程序可以与用户数据的精度分开地选择常量值的精度。

$ python3 decimal_instance_context.py

PI    : 3.14
RESULT: 6.3114

线程

“全局”上下文实际上本地是线程,因此可以使用不同的值来配置每个线程。

decimal_thread_context.py

import decimal
import threading
from queue import PriorityQueue

class Multiplier(threading.Thread):
    def __init__(self, a, b, prec, q):
        self.a = a
        self.b = b
        self.prec = prec
        self.q = q
        threading.Thread.__init__(self)

    def run(self):
        c = decimal.getcontext().copy()
        c.prec = self.prec
        decimal.setcontext(c)
        self.q.put((self.prec, a * b))

a = decimal.Decimal('3.14')
b = decimal.Decimal('1.234')
# PriorityQueue 将返回按精度排序的值,
# 无论线程完成时是什么顺序。
q = PriorityQueue()
threads = [Multiplier(a, b, i, q) for i in range(1, 6)]
for t in threads:
    t.start()

for t in threads:
    t.join()

for i in range(5):
    prec, value = q.get()
    print('{}  {}'.format(prec, value))

此示例使用指定的内容创建新上下文,然后在每个线程中安装它。

$ python3 decimal_thread_context.py

1  4
2  3.9
3  3.87
4  3.875
5  3.8748

也可以看看

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/decimal-...

译文地址:https://learnku.com/docs/pymotw/decimal-...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~