6

I'm trying to compute an approximation of the epsilon value for the float type (and I know it's already in the standard library).

The epsilon values on this machine are (printed with some approximation):

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

FLT_EVAL_METHOD is 2 so everything is done in long double precision, and float, double and long double are 32, 64 and 96 bit.

I tried to get an approximation of the value starting from 1 and dividing it by 2 until it becomes too small, doing all operation with the float type:

# include <stdio.h>

int main(void)
{
    float floatEps = 1;

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

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

The output is not what I was looking for:

float epsilon = 1.084202e-19

Intermediate operations are done with the greatest precision (due to the value of FLT_EVAL_METHOD), so this result seems legit.

However, this:

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

gives this output, which is the right one:

float epsilon = 1.192093e-07

but this one:

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

leads again to a wrong result, as the first one:

float epsilon = 1.084202e-19

These last two versions should be equivalent on this platform, is this a compiler bug? If not, what's happening?

Code is compiled with:

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

The gcc version is pretty old, but I'm at university and I can't update it:

$ 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)

Current version of gcc, 4.7, behaves correctly on my home computer. There are also comments saying that different versions give different results.

After some answers and comments, that clarified what is behaving as expected and what's not, I changed the question a little to make it clearer.

4

2 回答 2

7

允许编译器以float它喜欢的任何更高的精度来计算表达式,所以看起来第一个表达式是以long double精度计算的。在第二个表达式中,您强制将结果再次缩小float

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

正如您确认您的编译器已设置FLT_EVAL_METHOD为,2因此它使用最高精度进行任何浮点计算。

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

于 2013-04-17T15:18:06.357 回答
2

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

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

-1 不确定;

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

1 将 float 和 double 类型的运算和常量评估为 double 类型的范围和精度,将 long double 运算和常量评估为 long double 类型的范围和精度;

2 将所有运算和常量评估为 long double 类型的范围和精度。

由于历史原因,针对 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
于 2013-04-17T15:44:33.053 回答