ZetCode

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 位。

defprec.py
#!/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 比较浮点值

在比较浮点值时应谨慎。虽然在许多实际问题中,微小的误差是可以忽略的,但金融和货币计算必须精确。

comparing.py
#!/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)

该示例使用内置的 floatDecimal 类型执行浮点值比较。

$ ./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

alter_precision.py
#!/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.sqrtDecimalsqrtmpmath.sqrt 函数的精度。

$ alter_precision.py
    math.sqrt: 0.37796447300922719758631274089566431939601898193359375
 decimal.sqrt: 0.37796447300922722721451653623418006081575131186892
  mpmath.sqrt: 0.37796447300922722721451653623418006081575131186892
 actual value: 0.3779644730092272272145165362341800608157513118689214

Python Decimal 舍入

Decimal 类型提供了多种舍入选项

rounding.py
#!/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 来处理有理数。

fract.py
#!/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 模块。

symbolic.py
#!/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 类型存在微小误差。

来源

Decimal 定点和浮点运算

在本教程中,我们使用了 Python Decimal 类型。

作者

我叫 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Python 教程