GCC обрабатывает сравнение с плавающей точкой по-разному на разных уровнях оптимизации - PullRequest
6 голосов
/ 22 сентября 2010

У меня есть простой код, который сравнивает два значения с плавающей запятой, чтобы проиллюстрировать проблему, которую я вижу с оптимизацией GCC, и надеюсь, что кто-то может помочь мне выяснить, почему вывод, который он производит, отличается при некоторых повторяющихся обстоятельствах.

Во-первых, я знаю, что плохо сравнивать значения с плавающей запятой с ==, потому что в мантиссе вы можете быть очень маленьким, но это не так в моем примере. Проблема, с которой я столкнулся, заключается в изменении выходных данных на основе 2 факторов 1) флаг оптимизации, который я передаю, и 2) если я раскомментирую строку std :: cout.

Почему код, создаваемый GCC, работает по-другому под -O2? Почему код, скомпилированный в -O2, работает, если я раскомментирую печать?

Вот код, который я тестирую:

#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;
}

При компиляции в linux / cygwin с -O0, -O1 и -O3 (g ++ -O test.cpp) я получаю следующий вывод:


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


Этот вывод правильный, вы можете видеть, что биты для чисел с плавающей запятой (new_val и old_val) идентичны. Когда я компилирую с флагом -O2 (g ++ -O2 test.cpp), я получаю следующее:


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


Я бы посчитал этот вывод неправильным. Несмотря на то, что эти два значения имеют одинаковый бит, их вычитание и проверка == показывают, что они разные. Если затем я раскомментирую строку std :: cout и пересоберу с флагом -O2 (g ++ -O2 test.cpp), я получу следующее:


$ ./a.exe
Случайный курс, который делает эту работу
old_val! = new_val
0,40c3126f
diff_val = -6.096
Случайный курс, который делает эту работу
old_val == new_val
40c3126f, 40c3126f
diff_val = 1.19209e-07


Это верно в этом old_val == new_val, даже если вычитание все еще показывает небольшую разницу.

Этот код также работает под -O2, если в футах 2000, а не 20000.

Может кто-нибудь объяснить, почему скомпилированный код ведет себя так? Я хочу знать, почему 2-битные идентичные значения с плавающей точкой нельзя сравнивать с ==.

gcc версия 3.4.4

1 Ответ

11 голосов
/ 22 сентября 2010

Уровень оптимизации и окружающий код могут влиять на то, выбираются ли значения, используемые в расчете diff_val, из памяти или из регистров.Процессор может использовать 80-битные внутренние регистры с плавающей запятой в одном случае и 32-битные значения с плавающей запятой из памяти в другом случае, что дает неожиданные результаты.

Еще одна причина избегать использования == для сравнений с плавающей запятой!

...