浮点运算:可能不安全地依赖特定比较



下面的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循环中获得预期的迭代次数,并隐含假设i == 0变为真。也就是说,像这样的计算倍数可以减少 1 以达到正好 0 吗?

我不仅想知道它是否不安全,更重要的是,我需要了解浮点型的什么才能解决这样的问题?如果有人决定性地知道这是否安全,是否有可能解释为什么会这样?

您可以使用十进制模块来了解浮点数(如 0.3(之间"隐藏"的内容:

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

请注意,Python 2.7 更改了浮点数的编写方式(repr(f)的工作方式(,因此它现在显示最短的字符串,如果您这样做,该字符串将给出相同的浮点数float(s)。这意味着repr(0.3) == '0.3'在 Python 2.7 中,但在早期版本中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

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

至于一般的循环终止,那么我可以提供一个线索:对于小于 253 的浮点数,IEEE 754 可以表示所有整数:

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

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

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

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

(a - b - c)

如果a(例如(是 109,并且 bc 都是 1,那么答案将是 109,而不是 109-2(我在这里假设单精度浮点数(。

然后是这个:

i = (a - b - c) / d

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

然后是这个:

i -= 1

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

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

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

最新更新