views:

59

answers:

1

I have some simple code that is comparing two float values to illustrate a problem I see with GCC's optimization and am hoping someone can help me figure out why the output it produces is different under some repeatable circumstances.

First, I know it's bad to compare float values with == because you can be off by some very small amount in the mantissa, however that is not the case in my example. The problem I have is the output changes based on 2 factors. 1) the optimization flag I pass in, and 2) if I uncomment the std::cout line.

Why does the code GCC produce run differently under -O2? Why does the code compiled under -O2 work if I uncomment the print?

Here is the code I am testing:

#include <iostream>

const float ft_to_m          =  (float)0.3048; 
const float m_to_ft          =  (float)3.28083989501;


float FeetToKilometers( float & Feet ) {
  float Kilometers;
  Kilometers = (ft_to_m * Feet) / 1000.;
  return Kilometers;
}

int main(void)
{
    float feet = 20000.;
    float old_val = 0;
    float new_val = FeetToKilometers(feet );
    float diff_val = 0;

    int *old_int = reinterpret_cast<int*>(&old_val);
    int *new_int = reinterpret_cast<int*>(&new_val);

    for (int i=0; i<2; i++)
    {

    new_val = FeetToKilometers(feet );
    diff_val = old_val-new_val;

    //std::cout << "Random COUT that makes this work" << std::endl;

        if(old_val==new_val)
    {
             std::cout << "old_val==new_val" << std::endl;
         std::cout << std::hex << *old_int << "," << std::hex << *new_int << std::endl;
             std::cout << "diff_val = " << diff_val <<std::endl;
    }
        else
        {
            std::cout << "old_val!=new_val" <<std::endl;
        std::cout << std::hex << *old_int << "," << std::hex << *new_int << std::endl;
            std::cout << "diff_val = " << diff_val <<std::endl;
            old_val=FeetToKilometers(feet);
        }
    }

    return 0;
}

When compiled on linux/cygwin with -O0, -O1, and -O3 ( g++ -O test.cpp ), I get the following output:


$ ./a.exe
old_val!=new_val
0,40c3126f
diff_val = -6.096
old_val==new_val
40c3126f,40c3126f
diff_val = 0


That output is correct, you can see the bits for the floats (new_val and old_val) are identical. When I compile with the -O2 flag ( g++ -O2 test.cpp ) I get the following:


$ ./a.exe
old_val!=new_val
0,40c3126f
diff_val = -6.096
old_val!=new_val
40c3126f,40c3126f
diff_val = 1.19209e-07


I would consider this output wrong. Even though the two values are the same bit wise, subtracting them and the == check indicate they are different. If I then uncomment the std::cout line, and rebuild with the -O2 flag ( g++ -O2 test.cpp ) I get the following:


$ ./a.exe
Random COUT that makes this work
old_val!=new_val
0,40c3126f
diff_val = -6.096
Random COUT that makes this work
old_val==new_val
40c3126f,40c3126f
diff_val = 1.19209e-07


This is correct in that old_val == new_val, even though the subtraction still shows a slight difference.

This code also works under -O2 if feet is 2000, instead of 20000.

Can anyone explain why the compiled code is behaving like this? I want to know why 2 bit identical float values cannot be compared with ==.

gcc version 3.4.4

+8  A: 

The optimization level and surrounding code may affect whether the values used in the diff_val calculation are being fetched from memory, or from registers. The processor may be using 80-bit internal floating point registers in one case, and 32-bit floating point values from memory in the other case, giving unexpected results.

Yet another reason to avoid using == for floating point comparisons!

Jim Lewis
+1. This is (almost definitely) the issue. Compiling with the `-ffloat-store` option gives consistent results regardless of the optimization level.
eldarerathis
Okay so maybe I am a noob but how are you supposed to do floating point comparisons? <= and >= ? Just a link to the relevant info would be enough. Thanks.
Robert Massaioli
@Robert: `fabs(a-b) < tolerance` is less susceptible to these sorts of issues than `a == b`.
Jim Lewis
@Jim Lewis: That makes sense. Thanks for the response.
Robert Massaioli
@eldarerathis Thanks for the link to the gcc flag!
Cynan
@Jim Lewis -- Thanks, I suspected that was what was happening, but throwing the extra std::out in there and having it work just confused me!
Cynan