0 + 0 + 0... + 0 != 0

12
我有一个程序,在图中寻找路径并输出累积权重。图中所有边缘的个体权重为0至100,为浮点数,最多有两位小数。
在Windows/Visual Studio 2010上,对于由0权重边缘组成的特定路径,它将正确的总权重输出为0。然而,在Linux/GCC上,该程序说这条路径的权重为2.35503e-38。我有很多经验,这些经验都是由于浮点数引起的疯狂错误,但何时0 + 0会等于除0以外的其他结果呢?
我唯一能想到的可能引起这种情况的原因是程序将某些权重视为整数,并使用隐含的强制转换将它们添加到总和中。但是,0 + 0.0f仍然等于0.0f!
作为一个快速修复,当小于0.00001时,我将总数减少到0,这对我的需求已足够。但是,是什么造成了这种魔法呢?
注意:我非常有信心图形中没有任何超出我提到的范围的权重,并且在这条特定路径中所有的权重都是0。
编辑:详细说明一下,我已经尝试从文件中读取权重并在代码中手动设置它们等于0.0f。除将它们添加到总和中之外,没有进行任何其他操作。

19
你能构建一个最小化的测试用例吗? - Oliver Charlesworth
3
你如何确认在测试用例中权重实际上为零?最终的测试方法将会是打印并检查它们的十六进制二进制表示,或者尝试使用 assert(weight == 0.0f) - Emile Cormier
3
让我们接受一个事实:对于所有x,x * 0 == 0。如果 sum(xi) != 0,那么就存在 xi != 0。这可以通过采取逆否命题来证明。因此,我在数学上证明了其中一个权重实际上不为零。 :P - Emile Cormier
5
我敢打赌,你至少会添加一个未初始化的变量。 - rodrigo
5
未初始化的浮点数通常具有空指数,并且会给它们赋予在1e-401e-30范围内的值。如果您在路径中有任何其他非空值,则未初始化的值将是可以忽略的。 - rodrigo
显示剩余15条评论
3个回答

11

3
但问题是:为什么?(即,为什么不是零?)然而,目前还无法回答这个问题,因为我们需要看到原帖的测试案例。 - Oliver Charlesworth
1
IEEE浮点数可以精确地表示零,所以让我困惑的是为什么只有零的算术运算会导致非零结果。 - Emile Cormier
4
我认为你的评论可能是真实的,但dyffymo的答案太笼统了。 - zw324
1
@Austin Henley:为了澄清,如果你在方程式中所有的0都是0x00000000,那么0 + 0永远不会变成其他值,它只能是0。你可能在做错什么。在最好的情况下,你可能在代码的其他地方调用了未指定的行为。 - David Titarenco
3
我相信编译器——如果它说0 + 0 + 0 != 0,那么其中一个零不是真的零。 - duffymo
显示剩余8条评论

5

使用浮点数表示时最多保留两位小数。

不存在“最多保留两位小数”的浮点数。浮点数通常表示为二进制浮点数(分数二进制尾数和整数指数)。因此,许多(大多数)带有2个小数位的数字无法准确地表示。

例如,0.20f看起来像一个简单而圆滑的分数,但是

printf("%.40f\n", 0.20f);

将打印: 0.2000000029802322387695312500000000000000。

你看,它没有2个小数位, 而是26个!!!

当然,对于大多数实际用途来说,差异微不足道。但如果您进行某些计算,可能会增加舍入误差并使其可见,特别是在0附近。


我知道这对于非零值是成立的,但我的问题出现在处理仅为0的情况下。然而,如果有大量的零略微偏离,可能正如另一个答案所指出的那样,这可能会导致问题。 - Austin Henley
你怎么知道你正在处理0的确切值?它们是编译器常量吗?还是从文件中读取的? - rodrigo

3
可能你的浮点数包含值 "0.0f" 实际上并不是 0.0f(二进制表示为0x00000000),而是一个非常小的数字,接近于0.0。由于 IEEE754 规范定义了浮点格式,如果您有一个非常小的尾数和一个 0 指数,虽然它不等于绝对零,但会四舍五入为零。然而,如果您将这些数字相加足够多次,则非常小的数量将累积成一个最终会变为非零的值。
以下是一个例子,可以使 0 看起来像非零:
float f = 0.1f / 1000000000;
printf("%f, %08x\n", f, *(unsigned int *)&f);
float f2 = f * 10000;
printf("%f, %08x\n", f2, *(unsigned int *)&f2);

如果您将文字分配给变量并进行加法运算,那么编译器有可能没有将0翻译成内存中的0x0。如果已经进行了翻译,但仍然出现这种情况,则您的CPU硬件可能存在一个与将0转换为非零值相关的错误,这可能是他们验证努力中被忽略的问题。
然而,需要记住的是,IEEE浮点数只是一种近似值,而不是任何特定浮点值的精确表示。因此,任何浮点操作都必定会有一定的误差。

不,这与我的情况完全不同。我从未进行除法操作。我只是将0和0.0f分配给变量,然后将它们相加。 - Austin Henley
我举个例子来说明可能会导致这种错觉。当然,许多不同的情况可能会导致类似的情况。 - Ben Richards
没错,我从来没有想过添加常数0会导致这种情况。 - Austin Henley
在使用特定硬件时,可能存在错误。或者编译器没有将0x0分配给文字常量。这样的事情已经发生过(例如,我相信在旧版Pentium中有一个高调的错误)。 - Ben Richards
1
@sidran32:虽然有可能,但与其他潜在原因相比,这种情况非常罕见。 - Oliver Charlesworth
1
当然。但是如果他排除了其他所有可能性,这可能最终成为根本原因。回到高中时,我在我的C++课程中以类似的方式发现了CPU漏洞(当时得到了我的老师的确认)。听起来他已经足够自信,认为他的代码足够明确,其他原因很可能被排除了。这就是我提到它的原因。 - Ben Richards

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