微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决使用 python Decimal 包在金融应用中丢失浮点精度而感到困惑

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

准确结果是 197.925

使用十进制时,结果取决于操作顺序:

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 的认 Decimal 精度是否足够?

解决方法

就像通常的浮点运算一样,Python's 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 "units in the last place" (ULP)。通常情况下,涉及少量浮点运算的表达式会产生一些 ULP 舍入误差。所以这是按预期工作的。

我看到您已经尝试过 increasing Decimal's precision。这减少了舍入误差的幅度,但是是的,仍然存在舍入误差。

如果您不希望在打印结果的末尾看到尾随的“9”,例如使用 round(result,12) 将底部的几位数字舍去。

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

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

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

另请参阅 Python 文档 Floating Point Arithmetic: Issues and Limitations

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。