下面的python代码根据一些变量计算做事的迭代次数。
# a - b - c is always a multiple of d.
i = (a - b - c) / d
while i:
# do stuff
i -= 1
变量都将是相同的类型,即只有ints
或floats
或其他变量。我担心的是,如果值floats
,它是否会正常工作。我知道的足够多,可以始终考虑依赖精确浮点值的陷阱。但我无法判断上述内容是否危险。我可以使用i = int(round((a - b - c) / d))
,但我很好奇能更好地理解浮点数。
这一切都归结为以下内容:a - b - c
是d
的精确倍数。因此,我依靠(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,并且 b
和 c
都是 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,循环将结束。