为什么这个浮点数循环在1,000,000时终止?

4
这个样例作业问题的答案是“1,000,000”,但我不明白为什么。

What is the output of the following code?

int main(void) {
  float k = 1;
  while (k != k + 1) {
    k = k + 1;
  }
  printf(“%g”, k); // %g means output a floating point variable in decimal
}

If the program runs indefinitely but produces no output, write INFINITE LOOP as the answer to the question. All of the programs compile and run. They may or may not contain serious errors, however. You should assume that int is four bytes. You should assume that float has the equivalent of six decimal digits of precision. You may round your answer off to the nearest power of 10 (e.g., you can say 1,000 instead of 210 (i.e., 1024)).

我不明白为什么循环会终止。


3
这是一道抄袭作业或考试题的复制粘贴吗? - Doug T.
1
我倾向于询问提问者是否是在做作业,并让他们自己做决定。毕竟,他们想要作弊与否是他们自己的选择,而且他们愚蠢地认为教育者不会关注这样的网站。无论如何,有很多前例说明人们使用类似作业的问题进行自学。比如,像K&R或者自学某种技术的书籍里的所有问题都是如此。 - paxdiablo
2
这是一个示例问题,答案已经提供。但我无法找出如何得出答案,所以我向社区寻求帮助。我不是在作弊我的家庭作业。 - user133466
2
我怀疑,@marion,你只是没有等够长时间。这是一个好主意,为什么执行代码并不总是正确的做法。首先,你可能没有一个具有6位数字精度的浮点数。其次,你没有“思考”。即使是最愚蠢的科学家在盲目进行实验之前也会提出假设 :-) 没有任何冒犯之意。这个问题应该让你“思考”答案以及计算机的工作原理。 - paxdiablo
使用gcc编译器,我不得不改为while(1) { float j = k + 1; if (j == k) break;才能使其终止。使用优化时,我还需要加上-ffloat-store选项,尽管即使没有-ffloat-store,在-O2或-O3下它也会在2^65左右的某个地方最终终止。在我的机器上运行时间为0.2秒,因此除非marionmaiden的计算机非常老旧,否则我怀疑这不是因为缺乏耐心而导致的 :-) - Steve Jessop
显示剩余6条评论
4个回答

15

简单地说,它不能永远运行的原因是浮点数不完美。

在某个时候,k 变得足够大,加1对其没有影响。

此时,k 将等于 k+1,循环将退出。

只有当浮点数处于一定范围内时,它们才能被单个单位区分。

例如,假设您有一个整数类型,其具有3个小数位精度和一个单数字指数。

使用这个,您可以完美地表示 0 到 999 之间的数字,如 000x100 到 999x100(因为 100 是 1):

当您想要表示 1000 时会发生什么?您需要使用 100x101。这仍然可以完美地表示。

然而,使用这种方案没有准确的方法来表示 1001,下一个可以表示的数字是 101x101,即 1010。

所以,当你给 1000 加上 1 时,你会得到最接近的匹配,这是 1000。


5
代码使用了一个 float 变量。
根据问题的规定,float 具有6位数字精度,意味着第六位后的任何数字都将不准确。因此,一旦您超过一百万,最后一位将不准确,因此增加它将没有任何效果。

我建议您运行原帖作者的代码,看看您是完全错误的。在典型的实现中,结果将约为1600万(或者可能根本不会停止)。 - gnasher729

4
这个程序的输出结果在C标准中未指定,因为float类型的语义未指定。在IEEE-754单精度上运算时(float算术被计算的平台),一种可能的结果是2^24
所有小于2^24的整数都可以用单精度精确表示,因此计算不会在那个点停止。但是,在2^24之后的下一个可表示的单精度数是2^24 + 2。由于2^24+1恰好介于那个数字和2^24之间,在默认的IEEE-754舍入模式下会将其舍入为尾数为零的那个数字,即2^24
其他可能的答案包括2^532^64。还有其他可能的答案。例如,在默认舍入模式为向上取整的平台上,可能会产生Infinity(浮点数值)。正如其他人所指出的,也可能在将浮点表达式计算为更宽的类型的平台上出现无限循环(这是C标准允许的,但会引起各种程序员的困惑)。

3
实际上,在大多数C编译器上,这将无限循环,尽管精确的行为是实现定义的。
大多数编译器会给出无限循环的原因是它们在double精度下评估所有浮点表达式,并且只有在存储到变量时才将值舍入回float(单)精度。所以当k的值达到约2^24时,k == k + 1仍然会被评估为false(因为double可以容纳值k+1而不需要舍入),但是k = k + 1赋值将是空操作,因为k+1需要舍入才能适合于float。 编辑 x86上的gcc会得到这种无限循环的行为。有趣的是,在x64上它不会,因为它使用SSE指令在浮点精度下进行比较。

你尝试过哪些 C 编译器?我尝试了 GCC,它几乎瞬间停止了。 - Amok
即使您使用double类型,它也会在约1e16-1e17处停止。因为浮点数始终是有限的。浮点型数据具有6-7位数字精度,所以它在1e6处停止。双精度也是同理,具有16-17位数字精度。如果出现任何差异,那是因为您使用了不同的编译器选项进行浮点运算。 - phuclv
1
@LưuVĩnhPhúc:不,它不会停止。比较是以双精度完成的,但加法使用浮点数。因此,一旦浮点数达到2^24,它就不再增加,但比较永远不会失败。如果您对变量使用了双精度,则会在很长时间后停止(除非您有一个极其优化的编译器,它将结果设置为2^53)。 - gnasher729
@gnasher729 我在发表这条评论时没有考虑到晋升规则。 - phuclv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接