python - 对使用 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')
小数的精度可由用户设置
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 “最后一个单位”(ULP)。通常,涉及少量浮点运算的表达式会出现一些 ULP 舍入误差。所以这是按预期工作的。
我看到您已经尝试提高 Decimal 的精度。这降低了舍入误差的幅度,但是是的,仍然存在舍入误差。
如果您不希望在打印结果的末尾看到尾随的“9”,请使用例如round(result, 12)
舍入底部的几位数字。
或者,如果必须进行精确计算,我可以理解金融应用程序,那么不要使用 Decimal 或其他浮点表示。用分数将计算重新表述为算术:
Fraction(70000) * Fraction(5655, 1000) * Fraction(18) / Fraction(36000)
这恰好产生了正确的答案,7917/40 = 197.925。
另请参阅 Python 文档浮点算术:问题和限制。
推荐阅读
- timer - STM32:一个脉冲的多步增量定时器/计数器
- python - 部署到 AWS 服务器时 django 应用程序的 CSRF 令牌错误
- mysql - 这些表格符合条件吗?
- nginx - Nginx 后面的 Oracle Apex 重定向太多
- elasticsearch - AWS Elasticsearch 查询非常慢
- matlab - 为什么多天线 5g NR 链路比 1-1 天线链路更好(BER 更低)?
- python - 使用PIL替换每个像素的所有颜色(当前替换特定颜色
- delphi - 如何从 Delphi 模块中的访问冲突地址中查找代码?
- angular - Angular 12 中的 URL 屏蔽
- django - 字段 'id' 需要一个数字,但得到了 'product_id' django