终于有更新啦!
我有一个小故事。
我想在 MS Visual Studio 2008 编译的 C 程序(在 64 位 PC 上的 Windows 7 上运行)中计算机器 epsilon(满足条件 1.0 + epsilon = 1.0 的最大 epsilon > 0)。因为我知道 double 和 float 具有不同的精度,所以我想看看两者的答案。出于这个原因,我构建了以下程序:
#include <stdio.h>
typedef double float_type;
int main()
{
float_type eps = 1.0;
while ((float_type) 1.0 + eps / (float_type) 2.0 > (float_type) 1.0)
eps = eps / (float_type) 2.0;
printf("%g\n", eps);
return 0;
}
我很惊讶地看到它对 double 类型和 float 类型给出了相同的答案:2.22045e-16。这很奇怪,因为 double 占用的内存是 float 的两倍,而且应该更精确。之后我查看了维基百科并从那里获取了一个示例代码:
#include <stdio.h>
int main(int argc, char **argv)
{
float machEps = 1.0f;
do {
machEps /= 2.0f;
} while ((float)(1.0 + (machEps/2.0)) != 1.0);
printf( "\nCalculated Machine epsilon: %G\n", machEps );
return 0;
}
当它正常工作时,我更加惊讶!在尝试了解这两个程序之间的根本区别后,我发现了以下事实:如果我将循环条件更改为
while ((float_type) (1.0 + eps / (float_type) 2.0) > (float_type) 1.0)
嗯,你会说这是一个谜。哦,真正的谜团如下。相比:
while ((float) (1.0 + eps / 2.0f) > 1.0f)
它给出了正确的答案(1.19209e-07)和
while ((float) (1.0f + eps / 2.0f) > 1.0f)
它给出的答案对于浮点数不正确,对于双精度数(2.22045e-16)是正确的。
事实上这是完全错误的,结果应该是相反的。这是因为默认情况下,像 1.0 这样的常量被编译器视为双精度(根据标准),如果它存在于算术表达式中,则所有其他操作数都被提升为双精度。相反,当我写 1.0f 时,所有操作数都是浮点数,不应该进行任何提升。然而我得到了完全不同的结果。
在所有这些测试之后,我尝试使用 gcc 在 Linux 上编译运行程序。毫不奇怪,它完全符合我的预期(正确答案)。所以我现在猜测这是 Visual Studio 的错误。为了逗大家笑(如果还有人看过我的帖子,那还有什么值得怀疑的^_^)我再给大家做个对比:
float c = 1.0;
while ((float) (c + eps / 2.0f) > 1.0f)
这在VS中不能正常工作,但是......
const float c = 1.0;
给出 1.19209e-07 的正确答案。
请有人告诉我问题的根源是否是错误的 VS 2008 编译器是否正确(您能确认您机器上的错误吗?)。如果您在较新版本中测试此案例,我也将不胜感激:MS VS 2010。谢谢。
更新。 使用 MS Visual Studio 2013,我提到的第一个程序没有意外结果——它为 float 和 double 提供了适当的答案。我用所有浮点模型(精确、严格和快速)检查了这一点,没有任何改变。因此,在这种情况下,VS 2008 似乎确实存在问题。