Floating point arithmetic and epsilon machines

I am trying to calculate an approximation of the epsilon value for a float type (and I already know this in the standard library).

Epsilon values ​​on this machine (printed with some approximation):

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

FLT_EVAL_METHOD - 2 , so everything is done exactly long double and float , double and long double 32, 64 and 96 bits.

I tried to get an approximation of the value, starting from 1 and dividing it by 2 until it gets too small by doing all the operations 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 performed with maximum precision (due to the value of FLT_EVAL_METHOD ), so this result seems legitimate.

However, this:

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

gives this result, which is correct:

 float epsilon = 1.192093e-07 

but this one:

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

again leads to the wrong result, like the first:

 float epsilon = 1.084202e-19 

These last two versions should be equivalent on this platform, is this a compiler error? If not, what happens?

Code compiled with:

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

The gcc version is quite old, but I'm at the 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) 

The 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 behaves as expected and what doesn't, I slightly modified the question to make it clearer.

+5
c floating-point
source share
2 answers

The compiler is allowed to evaluate float expressions with any greater precision that he likes, so it seems that the first expression evaluates to exactly long double . In the second expression, you scale the result again to float .

In response to some of your additional questions and discussion below: you are basically looking for the smallest nonzero difference from 1 of the same floating point type. Depending on the setting of FLT_EVAL_METHOD compiler may decide to evaluate all floating point expressions with greater precision than the types used. On Pentium, traditionally internal floating point block registers are 80 bits, and the usability of this accuracy is for all types of small floating point. Therefore, ultimately your test depends on the accuracy of your comparison != . In the absence of an explicit cast, the accuracy of this comparison is determined by your compiler, not your code. When you click explicitly, you scale the comparison to the desired type.

As you have confirmed, your compiler set FLT_EVAL_METHOD to 2 , so it uses maximum precision for any floating point calculation.

As a conclusion to the discussion below, we are sure that there is an error related to the implementation of the case FLT_EVAL_METHOD=2 in gcc before version 4.5 and which has been fixed from at least version 4.6. If an integer constant 2 used in the expression instead of the floating-point constant 2.0 , then in the generated assembly, lowering is distinguished by float . It is also worth noting that from the optimization level -O1 correct results are produced on these older compilers, but the generated assembly is completely different and contains only a few floating point operations.

+6
source share

The C99 C compiler can evaluate floating point expressions as if they were a more accurate floating point type than their actual type.

The macro FLT_EVAL_METHOD set by the compiler to indicate the strategy:

-1 indefinable;

0 evaluate all operations and constants only by range and type accuracy;

1 evaluate operations and constants of type float and double to range and precision double type, evaluate long double operations and constants range and precision of long double type;

2 evaluate all operations and constants in the range and accuracy of a long double type.

For historical reasons, two common options when targeting x86 processors are 0 and 2.

The mc file is your first program. If I compile it using my compiler, I get:

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

If I compile this other program below, the compiler sets the macro according to what it does:

 #include <stdio.h> #include <float.h> int main(){ printf("%d\n", FLT_EVAL_METHOD); } 

Results:

 $ gcc -std=c99 -mfpmath=387 tc $ ./a.out 2 $ gcc -std=c99 tc $ ./a.out 0 
+2
source share

All Articles