Python Decimal 给出了错误的结果

goo*_*ion 2 python floating-point precision python-2.7

我在进行以下计算时遇到问题:

Decimal(3)*(Decimal(1)/Decimal(3))
Run Code Online (Sandbox Code Playgroud)

它不是返回 1.0,而是返回 0.999...

无论我如何提高Decimal模块的精度,情况仍然如此。

这是完整的代码:

from decimal import Decimal
from decimal import getcontext
getcontext().prec = 800
print Decimal(3)*(Decimal(1)/Decimal(3))
Run Code Online (Sandbox Code Playgroud)

具有讽刺意味的是,使用“本机”float解决了问题:

print float(3)*(float(1)/float(3))
Run Code Online (Sandbox Code Playgroud)

不用说,我在Decimal这里使用的是一个更复杂的计算,涉及大数的取幂。遇到这个问题后,我设法将其最小化为上面的示例。

Iza*_*gen 6

稍微扩展一下我的评论:

Python 中的十进制数实际上就是您可以手写的那种数字。重要的是,它不理解递归的概念,因此无限重复的分数就像1 / 3只是表示为0.333333333333..达到您的精度点。当它乘以 3 时,您会得到0.99999..- 这是明智的行为,因为它实际上无法知道那0.33333333..1 / 3在它被截断之后。Decimal由于除法时的舍入(实际上是除以除 2 或 5 以外的任何因子时),s 经常会因舍入而失去精度。如果能够进行除法至关重要,请使用 a Fraction,它代表任何有理数而不会损失分子和分母的任何精度:

In [1]: from fractions import Fraction

In [2]: Fraction(3) * Fraction(1, 3)
Out[2]: Fraction(1, 1)

In [3]: print(_)
1
Run Code Online (Sandbox Code Playgroud)

分数会自动简化。

有了你的浮动,我认为舍入误差已经消除只是运气问题。请注意,除非您需要绝对精度,否则floatorDecimal可能已经足够好,在这种情况下,我会推荐一个(例如,这意味着您可以可靠地进行相等性测试)。你总是可以四舍五入一个丑陋的数字,让它至少看起来更漂亮一点:Fraction

In [4]: "{:.2f}".format(Decimal(3) * (Decimal(1) / Decimal(3)))
Out[4]: '1.00'
Run Code Online (Sandbox Code Playgroud)

如果你正在做的事情就像一个模拟,之间的区别0.9999999,并1经常实际上不会多大关系。

另一种选择是重新调整您的操作顺序,以便保证分子可以被分母整除,如 Anilkumar 的回答所示。如果可能,这是一个很好的解决方案,但在某些情况下您可能无法这样做 - 例如,您期望结果是小数,或者您从某种黑匣子中获得小数被乘数。在这一点上,可以同时跟踪Decimal分子和分母......然后你意识到这就是Fraction类所做的,但麻烦更少。

请注意,您可以将 aFraction用于 aDecimal可以做的任何事情,但更重要的是。任何可表示的十进制数也是分数(10 的某个幂的尾数)。例如:

In [2]: Fraction("3.141")
Out[2]: Fraction(3141, 1000)
Run Code Online (Sandbox Code Playgroud)

自然,这会导致一些性能损失 - 分数必须跟踪更多数据,进行更多计算并且可能更抽象一些。

查看您新提供的公式 - 请注意,当您将有理数提高到非整数的幂时,结果可能不是有理数,因此您可能会在途中的某处丢失分数,例如:

>>> Fraction(1, 2) ** 4
Fraction(1, 16)
>>> Fraction(1, 2) ** 0.5
0.7071067811865476
Run Code Online (Sandbox Code Playgroud)

尽管在评估公式的上下文中,尝试以符号方式存储所有内容并没有多大意义。这让我们回到原石float通常足够好的想法。如果你真的想要某种 surd 输出格式,你可以试一试sympy

In [1]: from sympy import *

In [2]: sqrt(Integer(1) / Integer(2))
Out[2]: sqrt(2)/2
Run Code Online (Sandbox Code Playgroud)

这当然会让你更慢。