Python 3 十进制模块除法仍有浮点错误

Python 3 decimal module division still has floating point errors

提问人:GideonBear 提问时间:6/25/2022 最后编辑:Mark DickinsonGideonBear 更新时间:6/28/2022 访问量:307

问:

在 Python 中使用 decimal 模块时,除法似乎仍然给出不准确的结果。这是我用来检查的代码,是我做错了什么,还是不可能用十进制模块准确地进行除法?在这种情况下,我还能用其他东西来精确地进行这种计算吗?

from decimal import Decimal as D

a = D('39.37007874')
b = D('0.0254')
c = D('1')

print(c / a) #0.02540000000010160000000040640   unexpected
print(c / b) #39.37007874015748031496062992     unexpected
print(c * b) #0.0254                            expected
print(c * a) #39.37007874                       expected
python-3.x 十进制 精度

评论

2赞 codester_09 6/25/2022
为什么你认为输出不准确?这是准确的兄弟。
0赞 GideonBear 6/25/2022
@SharimIqbal 对不起,不应该是 0.0254 和 39.37007874 吗?我以为他们会。如果他们不应该这样做,有没有其他方法可以得到这个答案?
0赞 codester_09 6/25/2022
没有十进制模块更好。使用或直接使用a = float('39.37007874') b = float('0.0254') c = float('1')a = 39.37007874 b = 0.0254 c = 1
2赞 Sören 6/25/2022
>>> a*b 十进制('0.999999999996')
1赞 Sören 6/25/2022
如果您期望 a*b 为 1,那么是的,您的数字不准确。

答:

-1赞 d r 6/25/2022 #1

您可以简单地使用圆形函数并获取所需的任意数量的小数。

from decimal import Decimal as D

a = D('39.37007874')
b = D('0.0254')
c = D('1')

print(round(c / a, 8)) #0.02540000
print(round(c / b, 8)) #39.37007874
print(round(c * b, 8)) #0.02540000
print(round(c * a, 8)) #39.37007874 

评论

0赞 GideonBear 6/25/2022
有没有办法在不使用回合的情况下收到这个答案?我正在尝试使我的脚本尽可能准确。
1赞 Sören 6/25/2022
这两句话相互矛盾。0.025400000000010160000000040640 比 0.02540000 更准确,但出于某种原因您不希望这样。
0赞 d r 6/25/2022
正如 beeing 所说,您以这种方式选择精度是 2、6、10、20 ......小数。最准确的是代码中的那个。这完全取决于你以后要如何处理这些数字。以后,您可以随时根据脚本的逻辑和您的需要来格式化它们。也许你不需要在它们创建的时候对结果做任何事情。它们又好又准确。如果它们太准确,总会有圆形函数或某种格式或转换,任何适合您的东西。问候。。。
0赞 d r 6/25/2022
附言如果您愿意,这些数字中的每一个都可以有自己的小数位数。
-1赞 Monad 6/25/2022 #2

您可能希望使用 .fractions

from fractions import Fraction

a = Fraction('39.37007874')
b = Fraction('0.0254')
c = Fraction(1)

print(c / a)  # 50000000/1968503937
print(c / b)  # 5000/127
print(c * b)  # 127/5000
print(c * a)  # 1968503937/50000000

这将使除法尽可能准确,尽管其他操作(如 或)仍将与 一样准确。sqrt()sin()float

评论

0赞 GideonBear 6/25/2022
这确实使它尽可能准确,但在转换为浮点数时仍然给出相同的结果。问题是,我的输入数字不准确。
0赞 Diego Veralli 6/25/2022 #3

您可以通过设置全局上下文(用于中缀操作,如 )或使用实例执行操作来提高准确性:c / aContext

>>> from decimal import Decimal, Context, getcontext
>>> Decimal('1') / Decimal('0.0254')
Decimal('39.37007874015748031496062992')
>>> getcontext().prec
28
>>> getcontext().prec = 56
>>> Decimal('1') / Decimal('0.0254')
Decimal('39.370078740157480314960629921259842519685039370078740157')
>>> ctx = Context(prec=28)
>>> ctx.divide(Decimal('1'), Decimal('0.0254'))
Decimal('39.37007874015748031496062992')
>>> ctx = Context(prec=56)
>>> ctx.divide(Decimal('1'), Decimal('0.0254'))
Decimal('39.370078740157480314960629921259842519685039370078740157')

正如其他人所指出的,你对结果的期望可能是不正确的:39.37007874 * 0.0254 != 1

>>> from decimal import Decimal, Context
>>> ctx = Context(prec=1000000)
>>> ctx.multiply(Decimal('0.0254'), Decimal('39.37007874'))
Decimal('0.999999999996')

评论

0赞 ekhumoro 6/25/2022
这些都没有真正帮助,因为(如问题的评论中所述),OP 实际上是在尝试进行公制/英寸转换。因此,即使将英寸定义为 0.0254 米,也无法计算出一米中同样精确的英寸数。39.37007874英寸的数字只是一个近似值,因此根本不应将其用作计算的基础(假设最佳精度至关重要)。
0赞 Diego Veralli 6/25/2022
我忘记了 39.37007874,只用 0.0254 运算,我们可以根据需要使用十进制精度来调整结果的精度。这一点尤其重要,因为这会导致非终止小数,因此在转换为英寸时,我们会达到许多常见十进制值的精度限制。1 / 0.0254
0赞 ekhumoro 6/25/2022
是的,这正是我说的。这两个数字的精度不同,因此不应在同一计算中使用它们。您的答案仅使用准确率最低的数字,因此更改十进制上下文的精度具有误导性和错误性(即精度 != 准确性)。
0赞 Diego Veralli 6/25/2022
这是一个公平的观点,我将更新答案以使用 0.0254。我从未使用过英制系统,所以这两个数字都不是特别熟悉,我只是用两个数字中的一个来说明精度可以调整的想法。
0赞 ekhumoro 6/25/2022
我看到你的编辑,但它应该是“提高准确性”,而不是“精度”。我还认为值得指出的是,没有简洁的方法来定义一米的英寸数。我的老木工老师曾经说过一米是“三英尺三英寸八分之三”,相当于 39.375 英寸。显然可以比这更准确,但没有简单的分数可以给出真正精确的转换。因此,OP问题的答案是,他们应该将所有计算建立在25.4mm的基础上,然后将最终结果四舍五入到适当的精度。