在使用python Decimal包的金融应用程序中丢失浮点精度而感到困惑



我在计算70000.0*5.65500*18.0/36000.0时遇到了一个金融应用问题,并将结果与另一个数字进行比较。

准确结果为197.925

当使用Decimal时,结果取决于操作顺序:

from decimal import Decimal
from fractions import Fraction
Decimal('70000.0')*Decimal('5.65500')*Decimal('18.0')/Decimal('36000.0')
The result is Decimal('197.925000')
Decimal('70000.0')*Decimal('5.65500')/Decimal('36000.0')*Decimal('18.0')
The result is Decimal('197.9249999999999999999999999')

当使用Decimal + Fraction时,结果仍然不准确:

Decimal('70000.0')*Decimal('5.65500')*Decimal(float(Fraction(18, 36000)))
The result is Decimal('197.9250000000000041201417278')

当使用本机浮点数时,操作顺序不影响结果,但结果仍然不准确:

Decimal(70000.0*5.65500*18.0/36000.0)
The result is Decimal('197.92500000000001136868377216160297393798828125')
Decimal(70000.0/36000.0*5.65500*18.0)
The result is Decimal('197.92500000000001136868377216160297393798828125')

Decimal(1.0/36000.0)Decimal(5.655/36000.0)作为乘法器,其顺序几乎不影响结果,但结果仍然不准确:

Decimal('70000.0')*Decimal('5.65500')*Decimal('18.0')*Decimal(1.0/36000.0)
The result is Decimal('197.9250000000000094849096025')
Decimal('70000.0')*Decimal('5.65500')*Decimal(1.0/36000.0)*Decimal('18.0')
The result is Decimal('197.9250000000000094849096026')
Decimal('70000.0')*Decimal(5.655/36000.0)*Decimal('18.0')
The result is Decimal('197.9250000000000182364540136')
Decimal('70000.0')*Decimal('18.0')*Decimal(5.655/36000.0)
The result is Decimal('197.9250000000000182364540136')

如果没有办法达到绝对精确,容错可能是一种出路:比较容错内的两个数。

本机浮点数的精度为1E-14

Decimal(70000.0/36000.0*5.65500*18.0) - Decimal('197.925000')
The result is Decimal('1.136868377216160297393798828E-14')

默认设置的十进制精度为1E-25

Decimal('70000.0')*Decimal('5.65500')/Decimal('36000.0')*Decimal('18.0') - Decimal('197.925000')
The result is Decimal('-1E-25')

Decimal的精度可由用户

设置。
import decimal as decimal
from decimal import Decimal, Context
decimal.setcontext(Context(prec=60))
Decimal('70000.0')*Decimal('5.65500')/Decimal('36000.0')*Decimal('18.0')
The result is Decimal('197.924999999999999999999999999999999999999999999999999999999')

Decimal('70000.0')*Decimal('5.65500')*Decimal('18.0')/Decimal('36000.0')
The result is Decimal('197.925000')

Decimal(70000.0/36000.0*5.65500*18.0) - Decimal('197.925000')
The result is Decimal('1.136868377216160297393798828125E-14')

Decimal('70000.0')*Decimal('5.65500')/Decimal('36000.0')*Decimal('18.0') - Decimal('197.925000')
The result is  Decimal('-1E-57')

在金融应用中,为了保证绝对安全,是否推荐容错?默认的十进制容错精度为1E-20是否足够?

就像一般的浮点运算一样,Python的Decimal也不是精确的,并且会出现舍入错误。与float相比,Decimal的关键区别在于它使用以10为基数的表示,因此像"0.1"这样的值是完全可表示的

在第一个例子中,

Decimal('70000.0')*Decimal('5.65500')/Decimal('36000.0')*Decimal('18.0')
The result is Decimal('197.9249999999999999999999999')

在此计算中,Decimal使用其默认的28位精度。请注意,输出结果有28位有效数字,并且是正确的,直到最低的数字相差1。换句话说,计算在最后一个位置有1个单位的舍入误差。(ULP)。典型的情况是,涉及少量浮点操作的表达式会产生一些带有舍入错误的ulp。

我看到你已经尝试了增加Decimal的精度。这减少了舍入误差的大小,但是,仍然存在舍入误差。

如果您不希望在打印结果的末尾看到&;9&;s,则使用round(result, 12)将底部的几位数字四舍五入。

或者,如果精确的计算是必须的,我可以理解为一个金融应用程序,那么不要使用十进制或其他浮点表示。将计算重新表述为带有分数的算术:

Fraction(70000) * Fraction(5655, 1000) * Fraction(18) / Fraction(36000)

这恰好产生了正确的答案,7917/40 = 197.925。

请参阅Python文档浮点运算:问题和限制。

最新更新