1.4.2 浮点数运算
在实际应用中,特别是涉及金额计算时,我们需要对浮点数执行精确的计算操作,不希望有任何小误差的出现。
浮点数的一个问题是它不能精确地表示十进制数,即使是最简单的数学运算也会产生小的误差,相关代码(accurate_float.py)示例如下:
a = 2.1 b = 4.2 print(a + b)
这些误差是由底层CPU和IEEE 754标准通过自己的浮点运算单位去执行算术运算时产生的。由于Python的浮点数据类型使用底层表示存储数据,因此无法避免这样的误差。
如果想使浮点数运算更加精确(并能容忍一定的性能损耗),可以使用decimal模块,相关代码(accurate_float.py)示例如下:
from decimal import Decimal a = Decimal('2.1') b = Decimal('4.2') print(f'a + b = {a + b}')
初看起来,上面的代码好像有点奇怪,比如用字符串来表示数字。然而,Decimal对象会像普通浮点数一样工作(支持所有的常用数学运算)。如果打印Decimal对象或者在字符串格式化函数中使用Decimal对象,其看起来和普通数字一样。
decimal模块的一个主要特征是允许控制计算的数字位数和四舍五入运算。decimal模块首先创建一个本地上下文并更改它的设置,相关代码(accurate_float.py)示例如下:
from decimal import localcontext a = Decimal('1.3') b = Decimal('1.7') print(f'a / b = {a / b}') with localcontext() as ctx: ctx.prec = 3 print(f'a / b = {a / b}') with localcontext() as ctx: ctx.prec = 50 print(f'a / b = { a / b}')
decimal模块实现了IBM的通用小数运算规范。Python新手会倾向于使用decimal模块来处理浮点数的精确运算,不过先理解应用程序的目的是非常重要的。
如果你是在做科学计算、工程领域的计算或电脑绘图,那么使用普通的浮点类型是比较普遍的做法。其中一个原因是,在真实世界中很少要求提供17位精度的计算结果。另一个原因是,原生的浮点数计算要快得多,在执行大量运算的时候速度也是非常重要的。
当然,我们也不能完全忽略误差,也得注意减法、大数和小数加法运算所带来的影响,相关代码示例如下:
num_list = [1.23e+18, 1, -1.23e+18] print(f'sum result is: {sum(num_list)}')
上述误差可以利用math.fsum()方法来解决,示例如下:
import math print(f'math sum result: {math.fsum(num_list)}')
对于其他的算法,我们应该仔细研究它并理解它的误差产生来源。
总的来说,decimal模块主要用在涉及金融的领域。在金融领域,哪怕出现小小的误差也是不允许的。Python和数据库打交道的时候通常也会遇到Decimal对象,大多是在处理金融数据的时候。