Visual Studio中的Buggy浮点处理?

时间:2011-09-13 17:47:35

标签: visual-studio-2008 floating-point epsilon

最后有更新!

我有一个小故事。

我想在由MS Visual Studio 2008编译的C程序中计算机器epsilon(最大epsilon> 0,满足条件1.0 + epsilon = 1.0)(在64位PC上运行在Windows 7上)。既然我知道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。这很奇怪,因为双倍占用的内存比浮动多两倍,应该更精确。之后我查看了Wikipedia并从中获取了示例代码:

    #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)被编译器视为double(根据标准),如果它存在于算术表达式中,则所有其他操作数都被提升为double。相反,当我写1.0f时,所有操作数都是浮动的,不应该进行促销。然而,我得到了完全不同的结果。

在所有这些测试之后,我尝试使用gcc编译在Linux上运行程序。毫不奇怪,它打印完全符合我的预期(正确答案)。所以我现在猜测这是Visual Studio的bug。为了让你大笑(如果有人读过我的帖子,直到那一刻有什么可疑^ _ ^)我会给你另一个比较:

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似乎确实是错误的。

1 个答案:

答案 0 :(得分:1)

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

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

(float) (c + eps / 2.0f)

使用双精度完成c + eps / 2.0f。 3个操作数中的每一个都被提升为双精度,并且整个表达式被如此评估。当你施放它时,它只会向下舍入到浮点数。

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