浮点算术:可能不安全地依赖于具体的比较?

时间:2012-01-15 13:53:30

标签: python language-agnostic floating-point

以下python代码根据某些变量计算执行内容的迭代次数。

  # a - b - c is always a multiple of d.
  i = (a - b - c) / d
  while i:
    # do stuff
    i -= 1

变量都属于同一类型,只有intsfloats或其他类型。我担心的是,如果值为floats,它是否会正常工作。我知道足以始终考虑依赖于精确浮点值的陷阱。但我不知道上述情况是否危险。我可以使用i = int(round((a - b - c) / d)),但我很好奇能更好地理解浮点数。

这一切都归结为以下几点:a - b - cd的精确倍数。所以我依靠(a-b-c)/d成为一个值i,我可以从1中减去并获得while循环中预期的迭代次数,隐含的假设{{1}变得真实。也就是说,这样的计算倍数可以递减1来恰好达到0?

我不仅想知道它是否不安全,更重要的是,我需要了解浮点数来解决这样的问题?如果有人果断地知道这是否安全,是否有可能解释如何?

3 个答案:

答案 0 :(得分:4)

您可以使用decimal module来了解浮点数之间的“隐藏”,例如0.3

>>> from decimal import Decimal
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')

请注意,Python 2.7更改了浮点数的编写方式(repr(f)如何工作),以便它现在显示最短的字符串,如果执行float(s),它将提供相同的浮点数。这意味着Python 2.7中的repr(0.3) == '0.3',但早期版本中的repr(0.3) == '0.29999999999999999'。我之所以提到这一点,是因为当你真的想看看这些数字背后的内容时,它会让事情进一步混淆。

使用十进制模块,我们可以看到浮动计算中的错误:

>>> (Decimal(2.0) - Decimal(1.1)) / Decimal(0.3) - Decimal(3) 
Decimal('-1.85037170771E-16')

这里我们可能会期待(2.0 - 1.1) / 0.3 == 3.0,但是存在一个小的非零差异。但是,如果使用正常浮点数进行计算,则得到零:

>>> (2 - 1.1) / 0.3 - 3
0.0
>>> bool((2 - 1.1) / 0.3 - 3)
False

由于1.85e-16非零,因此结果在某处被舍入:

>>> bool(-1.85037170771E-16)
True

我不确定这种四舍五入的确切位置。

至于循环终止,我可以提供一条线索:for floats less than 253, IEEE 754 can represent all integers

>>> 2.0**53    
9007199254740992.0
>>> 2.0**53 + 1
9007199254740992.0
>>> 2.0**53 + 2
9007199254740994.0

如上所示,可表示数字之间的空间从2 53 到2 54 为2。但是如果你的i是一个小于2 53 的整数,那么i - 1也将是一个可表示的整数,你最终会命中0.0,这被认为是假的在Python中。

答案 1 :(得分:3)

我会给你一个与语言无关的答案(我真的不懂Python)。

您的代码中存在多个潜在问题。首先,这个:

(a - b - c)

如果a是(例如)10 9 bc都是1,那么答案将是10 9 ,而不是10 9 -2(我假设这里有单精度浮点数)。

然后是这个:

i = (a - b - c) / d

如果分子和分母是无法用浮点精确表示的数字(例如0.3和0.1),则结果可能不是精确整数(可能是3.0000001而不是3)。因此,您的循环永远不会终止。

然后是这个:

i -= 1

与上面类似,如果i当前是10 9 ,那么此操作的结果仍将是10 9 ,因此您的循环将永远不会终止

因此,您应该强烈考虑以整数运算执行所有计算。

答案 2 :(得分:1)

你是对的,零上可能存在非收敛性(至少比你想要的迭代次数多)。为什么不进行测试:while i >= 1。在这种情况下,与整数一样,如果你的i值低于1,那么循环将结束。