浮点算术和机器epsilon

时间:2013-04-17 15:13:35

标签: c floating-point

我正在尝试计算float类型的epsilon值的近似值(我知道它已经在标准库中)。

这台机器上的epsilon值(用一些近似值打印):

 FLT_EPSILON = 1.192093e-07
 DBL_EPSILON = 2.220446e-16
LDBL_EPSILON = 1.084202e-19

FLT_EVAL_METHOD2所以一切都以long double精度完成,floatdoublelong double为32,64和96位。

我试图从1开始得到一个近似值并将其除以2直到它变得太小,用float类型执行所有操作:

# include <stdio.h>

int main(void)
{
    float floatEps = 1;

    while (1 + floatEps / 2 != 1)
        floatEps /= 2;

    printf("float eps = %e\n", floatEps);
}

输出不是我想要的:

float epsilon = 1.084202e-19

中间操作以最高精度完成(由于FLT_EVAL_METHOD的值),因此这个结果似乎是合法的。

但是,这个:

// 2.0 is a double literal
while ((float) (1 + floatEps / 2.0) != 1)
    floatEps /= 2;

给出了这个输出,这是正确的输出:

float epsilon = 1.192093e-07

但是这一个:

// no double literals
while ((float) (1 + floatEps / 2) != 1)
    floatEps /= 2;

再次导致错误的结果,如第一个:

float epsilon = 1.084202e-19

这个最后两个版本在这个平台上应该是等价的,这是一个编译器错误吗?如果没有,发生了什么?

代码编译为:

gcc -O0 -std=c99 -pedantic file.c

gcc版本很老了,但是我在大学,我无法更新它:

$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8'
--with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4
--enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls
--enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc
--enable-targets=all --with-arch-32=i586 --with-tune=generic
--enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu
--target=i486-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8)

当前版本的gcc 4.7在我的家用电脑上运行正常。还有评论说不同版本会产生不同的结果。

在得到一些答案和评论之后,我澄清了什么是预期的行为,什么不是,我改变了一点问题,使其更加清晰。

2 个答案:

答案 0 :(得分:7)

允许编译器以任何更高的精度评估float表达式,因此看起来第一个表达式以long double精度计算。在第二个表达式中,您强制将结果再次缩放到float

在回答您的一些其他问题和下面的讨论时:您基本上是在寻找一些浮点类型中最小的非零差异。根据{{​​1}}的设置,编译器可能决定以比所涉及的类型更高的精度评估所有浮点表达式。在奔腾上,传统上浮点单元的内部寄存器是80位,并且对于所有较小的浮点类型使用该精度很方便。因此,最后您的测试取决于您的比较FLT_EVAL_METHOD的精确度。在没有显式强制转换的情况下,此比较的精度由编译器确定,而不是由您的代码确定。通过显式转换,您可以将比较缩小到您想要的类型。

当您确认编译器已将!=设置为FLT_EVAL_METHOD时,它会对任何浮点计算使用最高精度。

作为以下讨论的结论,我们有信心说在版本4.5之前的2中存在与FLT_EVAL_METHOD=2案例的实施相关的错误,并且至少从版本修复4.6。如果在表达式中使用整数常量gcc而不是浮点常量2,则在生成的程序集中省略对2.0的强制转换。值得注意的是,从优化级别float开始,在这些较旧的编译器上生成了正确的结果,但生成的程序集完全不同,只包含很少的浮点运算。

答案 1 :(得分:2)

C99 C编译器可以评估浮点表达式,就好像它们的浮点类型比实际类型更精确。

FLT_EVAL_METHOD由编译器设置以指示策略:

  

-1 indeterminable;

     

0仅根据类型的范围和精度评估所有操作和常量;

     

1评估操作和   float类型的常量和double的范围和精度   double类型,计算long double操作和常量   长双精度的范围和精度;

     

2评估全部   操作和常数的范围和精度的长   双重型。

由于历史原因,在定位x86处理器时有两个常见选择:0和2。

文件m.c是您的第一个程序。如果我使用我的编译器编译它,那么,我得到:

$ gcc -std=c99 -mfpmath=387 m.c
$ ./a.out 
float eps = 1.084202e-19
$ gcc -std=c99  m.c
$ ./a.out 
float eps = 1.192093e-07

如果我编译下面的其他程序,编译器会根据它的作用设置宏:

#include <stdio.h>
#include <float.h>

int main(){
  printf("%d\n", FLT_EVAL_METHOD);
}

结果:

$ gcc -std=c99 -mfpmath=387 t.c
$ ./a.out 
2
$ gcc -std=c99 t.c
$ ./a.out 
0