【Python Cookbook】S03E02 执行精确的小数计算 decimal, math.fsum()

问题

如果我们需要对小数执行精确的计算,并且不希望因为浮点数的误差带来影响,我们该怎么做?

解决方案

关于浮点数,一个人尽皆知的问题就是其无法精确地表达出所有十进制小数位,因此甚至简单的数字也会引入微小的误差。

a = 4.2
b = 2.1
print(a + b)
print((a + b) == 6.3)

>>> 6.300000000000001
>>> False

而浮点数出现误差的原因,是因为在 Python 中,浮点数遵循 IEEE 754 标准,这点导致某些十进制的小数在转换为二进制数时,无法精确表示,如

# 十进制中,0.1 表示为
x = 0.1
# 但是在转换计算机的二进制时,0.1转换为
0.1 * 2 -> 整数部分0, 0.2 * 2 -> 整数部分0, 0.4 * 2 -> 整数部分0, 0.8 * 2 -> 整数部分1, 0.6 * 2 -> 整数部分1, 0.2 * 2 -> 整数部分0, ...
即b(x) = 0.000110001100011...

由于计算内存有限,不能存储无限循环小数,必须对小数进行四舍五入或者截断,至此,产生误差。

此时,如果我们需要更高的精度,则可以使用 decimal 模块

from decimal import Decimal

a = Decimal('4.2')
b = Decimal('2.1')
print(a + b)
print(a + b == Decimal('6.3'))

之所以 Decimal 对象可以精确计算,根本原因是因为其使用了不同于 IEEE 754 的表示形式来存储数值。Decimal 对象基于十进制数,而非二进制数,不直接在硬件上运行,而是以软件模拟十进制的方式进行计算。优点在于的确能够提供精确的十进制表示和计算,但是缺点很明显,区别于直接在硬件上计算,其会运行的较慢。

此外,decimal 模块还可控制计算过程中的各个方面,比如数字的位数以及四舍五入等等。其需要创建一个本地的上下文环境然后修改其设定。

from decimal import localcontext, Decimal

a = Decimal('1.3')
b = Decimal('1.7')
print(a / b)
with localcontext() as ctx:
    ctx.prec = 3
    print(a / b)

讨论

其实在真实世界中,极少有什么东西需要计算到小数点后17位,因此,在计算中引入微小误差不足挂齿。而且明显原生依托于硬件计算的浮点数运算性能要快上很多,如果要执行大量的计算,那性能问题就非常重要了。

这也就是说,误差就像病菌一样,无法完全忽略误差。很多时候,我们需要按照项目需求以及任务类型来做选择。

# 尤其要注意大数和小数加在一起的时候
nums = [1.23e+18, 1, -1.23e+18]
print(sum(nums))
>>> 0
# 通过使用 math.fsum() 方法解决
import math
print(math.fsum(nums))
>>> 1

综上所述,decimal 并非浮点数运算的最佳方案,或者说,没有最佳方案,选择只有最合适的吧。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脚踏实地的大梦想家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值