5

终于有更新啦!

我有一个小故事。

我想在 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 似乎确实存在问题。

4

1 回答 1

1

默认情况下,Visual Studio 的浮点设置设置为“精确”。这意味着它将尝试使结果尽可能精确。这样做的副作用之一是中间体被提升为双精度。

虽然我没有查看您发布的所有代码,但我怀疑问题出在这里:

(float) (c + eps / 2.0f)

c + eps / 2.0f是使用双精度完成的。3 个操作数中的每一个都被提升为双精度,并且整个表达式都是这样计算的。当你施放它时,它只会四舍五入为浮点数。

如果将浮点模式设置为“严格”,它应该可以按预期工作。

于 2011-09-13T17:53:27.773 回答