Python Decimal
最后修改于 2024 年 1 月 29 日
在本文中,我们将展示如何使用 Decimal 在 Python 中执行高精度计算。
Python decimal
Python decimal 模块支持快速、正确舍入的十进制浮点运算。
默认情况下,Python 将任何包含小数点的值解释为双精度浮点数。Decimal 是一种浮点十进制类型,它比 float 具有更高的精度和更小的范围。它适用于金融和货币计算。它也更接近人类处理数字的方式。
与基于硬件的二进制浮点数不同,decimal 模块具有用户可变的精度,该精度可以根据给定问题的需要而增大。默认精度为 28 位。
某些值无法在 float 数据类型中精确表示。例如,将 0.1 值存储在 float(即二进制浮点值)变量中,我们只能得到该值的近似值。同样,1/3 的值也无法在十进制浮点类型中精确表示。
这两种类型都不是完美的;一般来说,十进制类型更适合金融和货币计算,而双精度/浮点类型更适合科学计算。
Python Decimal 默认精度
Decimal 的默认精度为 28 位,而 float 的精度为 18 位。
#!/usr/bin/python
from decimal import Decimal
x = 1 / 3
print(type(x))
print(x)
print("-----------------------")
y = Decimal(1) / Decimal(3)
print(type(y))
print(y)
该示例比较了 Python 中两种浮点类型的精度。
$ ./defprec.py <class 'float'> 0.3333333333333333 ----------------------- <class 'decimal.Decimal'> 0.3333333333333333333333333333
Python 比较浮点值
在比较浮点值时应谨慎。虽然在许多实际问题中,微小的误差是可以忽略的,但金融和货币计算必须精确。
#!/usr/bin/python
from decimal import Decimal
x = 0.1 + 0.1 + 0.1
print(x == 0.3)
print(x)
print("----------------------")
x = Decimal('0.1') + Decimal('0.1') + Decimal('0.1')
print(x == Decimal('0.3'))
print(float(x) == 0.3)
print(x)
该示例使用内置的 float 和 Decimal 类型执行浮点值比较。
$ ./comparing.py False 0.30000000000000004 ---------------------- True True 0.3
由于 float 类型存在微小误差,0.1 + 0.1 + 0.1 == 0.3 的结果为 False。而使用 Decimal 类型,我们可以得到预期的结果。
Python Decimal 更改精度
可以更改 Decimal 类型的默认精度。在下面的示例中,我们还使用了 mpmath 模块,这是一个用于任意精度浮点运算的库。
$ pip install mpmath
我们首先需要安装 mpmath。
#!/usr/bin/python
from decimal import Decimal, getcontext
import math
import mpmath
getcontext().prec = 50
mpmath.mp.dps = 50
num = Decimal(1) / Decimal(7)
num2 = mpmath.mpf(1) / mpmath.mpf(7)
print(" math.sqrt: {0}".format(Decimal(math.sqrt(num))))
print("decimal.sqrt: {0}".format(num.sqrt()))
print(" mpmath.sqrt: {0}".format(mpmath.sqrt(num2)))
print('actual value: 0.3779644730092272272145165362341800608157513118689214')
在示例中,我们将精度更改为 50 位。我们比较了 math.sqrt、Decimal 的 sqrt 和 mpmath.sqrt 函数的精度。
$ alter_precision.py
math.sqrt: 0.37796447300922719758631274089566431939601898193359375
decimal.sqrt: 0.37796447300922722721451653623418006081575131186892
mpmath.sqrt: 0.37796447300922722721451653623418006081575131186892
actual value: 0.3779644730092272272145165362341800608157513118689214
Python Decimal 舍入
Decimal 类型提供了多种舍入选项
- 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,则远离零舍入,否则向零舍入
#!/usr/bin/python
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',
]
col_lines = '-' * 10
print(f"{' ':20} {'1/7 (1)':^10} {'1/7 (2)':^10} {'1/7 (3)':^10} {'1/7 (4)':^10}")
print(f"{' ':20} {col_lines:^10} {col_lines:^10} {col_lines:^10} {col_lines:^10}")
for mode in rounding_modes:
print(f'{mode:20}', end=' ')
for precision in [1, 2, 3, 4]:
context.prec = precision
context.rounding = getattr(decimal, mode)
value = decimal.Decimal(1) / decimal.Decimal(7)
print(f'{value:<10}', end=' ')
print()
print('********************************************************************')
print(f"{' ':20} {'-1/7 (1)':^10} {'-1/7 (2)':^10} {'-1/7 (3)':^10} {'-1/7 (4)':^10}")
print(f"{' ':20} {col_lines:^10} {col_lines:^10} {col_lines:^10} {col_lines:^10}")
for mode in rounding_modes:
print(f'{mode:20}', end=' ')
for precision in [1, 2, 3, 4]:
context.prec = precision
context.rounding = getattr(decimal, mode)
value = decimal.Decimal(-1) / decimal.Decimal(7)
print(f'{value:<10}', end=' ')
print()
该示例展示了 1/7 和 -1/7 表达式可用的舍入选项。
$ ./rounding.py
1/7 (1) 1/7 (2) 1/7 (3) 1/7 (4)
---------- ---------- ---------- ----------
ROUND_CEILING 0.2 0.15 0.143 0.1429
ROUND_DOWN 0.1 0.14 0.142 0.1428
ROUND_FLOOR 0.1 0.14 0.142 0.1428
ROUND_HALF_DOWN 0.1 0.14 0.143 0.1429
ROUND_HALF_EVEN 0.1 0.14 0.143 0.1429
ROUND_HALF_UP 0.1 0.14 0.143 0.1429
ROUND_UP 0.2 0.15 0.143 0.1429
ROUND_05UP 0.1 0.14 0.142 0.1428
********************************************************************
-1/7 (1) -1/7 (2) -1/7 (3) -1/7 (4)
---------- ---------- ---------- ----------
ROUND_CEILING -0.1 -0.14 -0.142 -0.1428
ROUND_DOWN -0.1 -0.14 -0.142 -0.1428
ROUND_FLOOR -0.2 -0.15 -0.143 -0.1429
ROUND_HALF_DOWN -0.1 -0.14 -0.143 -0.1429
ROUND_HALF_EVEN -0.1 -0.14 -0.143 -0.1429
ROUND_HALF_UP -0.1 -0.14 -0.143 -0.1429
ROUND_UP -0.2 -0.15 -0.143 -0.1429
ROUND_05UP -0.1 -0.14 -0.142 -0.1428
Python Fraction
我们可以使用 Fraction 来处理有理数。
#!/usr/bin/python
from decimal import Decimal
from fractions import Fraction
x = Decimal(1) / Decimal(3)
y = x * Decimal(3)
print(y == Decimal(1))
print(x)
print(y)
print("-----------------------")
u = Fraction(1) / Fraction(3)
v = u * Fraction(3)
print(v == 1)
print(u)
print(v)
Decimal 无法精确表示 1/3 表达式。在某些情况下,我们可以使用 Fraction 类型来获得准确的结果。
$ ./fract.py False 0.3333333333333333333333333333 0.9999999999999999999999999999 ----------------------- True 1/3 1
Python SymPy Rational
也可以使用 SymPy 的 Rational 类型来处理有理数。
$ pip install sympy
我们安装 sympy 模块。
#!/usr/bin/python from sympy import Rational r1 = Rational(1/10) r2 = Rational(1/10) r3 = Rational(1/10) val = (r1 + r2 + r3) * 3 print(val.evalf()) val2 = (1/10 + 1/10 + 1/10) * 3 print(val2)
在示例中,我们比较了 (1/10 + 1/10 + 1/10)*3 表达式的 Rational 和内置 float 的精度。
$ ./symbolic.py 0.900000000000000 0.9000000000000001
对于 float 类型存在微小误差。
来源
在本教程中,我们使用了 Python Decimal 类型。
作者
列出所有 Python 教程。