Проблема GCC с необработанными сравнениями двойного типа - PullRequest
8 голосов
/ 23 марта 2010

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

#include <iostream>

int main()
{
   const unsigned int cnt = 10;
   double lst[cnt] = { 0.0 };
   const double v[4] = { 131.313, 737.373, 979.797, 731.137 };

   for(unsigned int i = 0; i < cnt; ++i) {
      lst[i] = v[i % 4] * i;
   }

   for(unsigned int i = 0; i < cnt; ++i) {
      double d = v[i % 4] * i;
      if(lst[i] != d) {
         std::cout << "error @ : " << i << std::endl;
         return 1;
      }
   }
   return 0;
}
  • при компиляции с: "g ++ -pedantic -Wall -Werror -O1 -o test test.cpp" Я получаю следующий вывод: "error @: 3 «

  • при компиляции с: "g ++ -pedantic -Wall -Werror -O2 -o test test.cpp" Я получаю следующий вывод: "error @: 3 «

  • при компиляции с: "g ++ -pedantic -Wall -Werror -O3 -o test test.cpp" Я не получаю ошибок

  • при компиляции с: "g ++ -pedantic -Wall -Werror -o test test.cpp" Я не получаю ошибок

Я не считаю, что это проблема, связанная с округлением или эпсилон-разницей в сравнении. Я пробовал это с Intel v10 и MSVC 9.0, и все они, кажется, работают как ожидалось. Я считаю, что это должно быть не более чем побитовое сравнение.

Если я заменю оператор if следующим: if (static_cast<long long int>(lst[i]) != static_cast<long long int>(d)) и добавлю "-Wno-long-long", я не получу ошибок ни в одном из режимов оптимизации при запуске.

Если я добавлю std::cout << d << std::endl; до «return 1», я не получу ошибок ни в одном из режимов оптимизации при запуске.

Это ошибка в моем коде, или что-то не так с GCC и тем, как он обрабатывает тип double?

Примечание: Я только что попробовал это с gcc версий 4.3 и 3.3, ошибка не отображается.

Разрешение: Майк Динсдейл отметил следующее сообщение об ошибке: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 Кажется, что команда GCC не не совсем уверена в природе проблемы.

Как предлагается в отчете об ошибках, возможное решение - использовать опцию ffloat-store. Я попробовал это, и это работает, однако результаты с точки зрения производительности не так уж хороши, хотя ymmv.

Ответы [ 3 ]

7 голосов
/ 23 марта 2010

Тот факт, что результат зависит от настроек оптимизации, говорит о том, что это может быть расширенная точность x87 с вещами (как говорит Майкл Барр).

Вот код, который я использую (с gcc на процессорах x86), чтобы отключить расширенную точность:

static const unsigned int PRECISION_BIT_MASK = 0x300;
///< bitmask to mask out all non-precision bits in the fpu control word \cite{INTEL}.
static const unsigned int EXTENDED_PRECISION_BITS = 0x300;
///< add to the fpu control word (after zeroing precision bits) to turn on extended precision \cite{INTEL}.
static const unsigned int STANDARD_PRECISION_BITS = 0x200;
///< add to the fpu control word (after zeroing precision bits) to turn off extended precision \cite{INTEL}.

void set_fpu_control_word(unsigned int mode)
{
  asm ("fldcw %0" : : "m" (*&mode));
}

unsigned int get_fpu_control_word()
{
  volatile unsigned int mode = 0;
  asm ("fstcw %0" : "=m" (*&mode));
  return mode;
}

bool fpu_set_extended_precision_is_on(bool state)
{
  unsigned int old_cw = get_fpu_control_word();
  unsigned int masked = old_cw & ~PRECISION_BIT_MASK;
  unsigned int new_cw;
  if(state)
    new_cw = masked + EXTENDED_PRECISION_BITS;
  else
    new_cw = masked + STANDARD_PRECISION_BITS;
  set_fpu_control_word(new_cw);
  return true;
}

bool fpu_get_extended_precision_is_on()
{
  unsigned int old_cw = get_fpu_control_word();
  return  ((old_cw & PRECISION_BIT_MASK) == 0x300);
}

Или вы можете просто запустить свой код с помощью valgrind, который не имитирует 80-битные регистры и, вероятно, проще для такой короткой программы, как эта!

4 голосов
/ 23 марта 2010

Вероятно, проблема связана с потерей некоторой точности при сохранении результата выражения по сравнению с тем, что компилятор не сделал этого в локальной системе в качестве оптимизации:

double d = v[i % 4] * i;  // the result, `d`, might be kept in a register 
                          //   instead of storing it in a memory location, 
                          //   keeping full precision

if(lst[i] != d) {         // the value stored in lst[i] may have lost some 
                          //   precision since it had to be stored in memory,
                          //   which might not be able to hold the full 
                          //   precision that the expression generated

Стандарт C99 говорит в 6.3.1.8/2 "Обычные арифметические преобразования":

Значения плавающих операндов и результатов плавающих выражений могут быть представлены с большей точностью и дальностью, чем требуется типом; типы не тем самым изменилось.

3 голосов
/ 23 марта 2010

Ширина регистров с плавающей запятой в x86 отличается от ширины double в ОЗУ. Поэтому сравнение может быть успешным или неуспешным в зависимости от того, как компилятор решит оптимизировать загрузку значений с плавающей запятой.

...