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)
要更改精度,请将 1
和 decimal.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
如果最后一位是 0
或 5
,则舍入为零,否则为零。
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
也可以看看
- decimal 标准库文档
- Python 2 到 3 decimal 的迁移笔记
- 维基百科:浮点 -- 关于浮点表示和算术的文章。
- 浮点算术:问题和局限 -- Python 教程中的文章描述了浮点在数学中的表示问题。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。