欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

0.1+0.2!=0.3, why?

程序员文章站 2022-04-26 15:42:25
...

0.1+0.2!=0.3, why?

0.1+0.2!=0.3, why?


通常情况下,在进行浮点运算时,会出现一些“匪夷所思”的结果。


我们都知道,1/3 等于 0.333…(无限循环),0.1 + 0.2 等于 0.3,但 Python 似乎不这么认为:

>>> 1/3
0.3333333333333333
>>>
>>> 0.1 + 0.2 == 0.3
False
>>>
>>> 0.1 + 0.2
0.30000000000000004

What?难道是眼花了?还有这种操作?(一脸懵逼。。。)

1

为何出现这种情况?

在看到上面出现的“错误”时,你可能会非常震惊,但它只不过是有关“浮点表示错误”的一个典型示例,类似的情况还有很多:

>>> 0.2 + 0.4        # 加法
0.6000000000000001
>>>
>>> 0.3 - 0.2
0.09999999999999998  # 减法
>>>
>>> 9.7 * 100
969.9999999999999    # 乘法
>>>
>>> 0.3 / 0.1
2.9999999999999996   # 除法

之所以会这样,与浮点数在内存中的存储方式有关:

在大多数现代计算机中,浮点数会被存储为精度为 53 位的二进制小数,只有具有有限的二进制小数表示法(可以用 53 位表示)的数字才被存储为一个精确的值,但并不是每个数字都有一个有限的二进制小数表示法。

例如,小数 0.1 有一个有限的十进制表示,但二进制表示却是无限的。正如分数 1/3 只能表示为无限循环小数 0.333…,分数 1/10 只能用二进制表示为无限循环小数 0.0001100110011… 一样。

具体请参考官方文档:Floating Point Arithmetic: Issues and Limitations(https://docs.python.org/3/tutorial/floatingpoint.html)。

出于这个原因,大部分小数都不能准确地存储在计算机中。因此,这是计算机硬件的局限性,而不是 Python 中的 bug。

2

精确处理

对于浮点运算过程中产生的一些误差,在有些需要精确计算的场合(例如:财务结算)是不可接受的。

好在 decimal 模块解决了这个烦恼 ,虽然浮点数的默认精度可达 17 位,但 decimal 模块可自定义精度:

>>> import decimal
>>>
>>> 0.1
0.1
>>>
>>> decimal.Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>>
>>> decimal.getcontext()           # 当前上下文
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[FloatOperation], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> decimal.getcontext().prec      # 精度默认 28 位
28
>>>
>>> d = decimal.Decimal(1) / decimal.Decimal(9)
>>> d
Decimal('0.1111111111111111111111111111')
>>>
>>> decimal.getcontext().prec = 3  # 将精度修改为 3 位
>>>
>>> d = decimal.Decimal(1) / decimal.Decimal(9)
>>> d
Decimal('0.111')

除此之外,还可以用它进行精确计算:

>>> 1.2 * 2.50
3.0
>>>
>>> from decimal import Decimal as D
>>>
>>> D('0.1') + D('0.2')  # 这时的结果变为了 0.3
Decimal('0.3')
>>>
>>> D('1.2') * D('2.50')  
Decimal('3.00')          # 注意计算结果末尾的 0

注意:2.50 比 2.5 更精确,因为它有两个有效的小数位。

有人可能会问:既然 Decimal 这么好,为什么不每次都使用 Decimal 来代替 float 呢?其实主要是效率的原因,因为 float 运算要比 Decimal 运算更快。

那么,应该在何时使用 Decimal,而不是 float 呢?主要有以下情况:

  • 当在做金融应用时,需要精确表示;

  • 当想要控制所需的精度级别时;

  • 当想要实现有效的小数位概念时;

  • 当想要像在学校里学到的那样进行小数运算时。

3

浮点数比较

在处理浮点数计算时需要非常小心,尤其是进行浮点数的大小比较。

早期版本

如果是早期的  Python 版本,可以用下述方式进行比较:

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

可参考文档 PEP 485(https://www.python.org/dev/peps/pep-0485/#proposed-implementation)。

3.5 及之后的版本

在 Python 3.5 中,引入了 math.isclose() 和 cmath.isclose() 方法,用于判断两个值是否大致相等或相互“接近”,比较结果取决于 rel_tol 和 abs_tol。

  • rel_tol:相对容差 - (相对于 a 或 b 的较大绝对值)允许的误差量。

  • abs_tol:是一个最小的绝对容差范围 -- 对于接近于零的比较非常有用。

例如,指定 rel_tol 来比较两个值:

>>> import math
>>> a = 5.0
>>> b = 4.99998
>>> math.isclose(a, b, rel_tol=1e-5)
True
>>> math.isclose(a, b, rel_tol=1e-6)
False

同样地,也可以指定 abs_tol 进行比较:

>>> a = 5.0
>>> b = 4.99998
>>> math.isclose(a, b, abs_tol=0.00003)
True
>>> math.isclose(a, b, abs_tol=0.00001)
False

·END·
 

高效程序员

谈天 · 说地 · 侃代码 · 开车

0.1+0.2!=0.3, why?

长按识别二维码,解锁更多精彩内容