Аномалия с плавающей точкой, когда неиспользованное утверждение не закомментировано? - PullRequest
8 голосов
/ 24 ноября 2011

Когда программа, как показано ниже, запускается, она выдает нормально:

 j=         0    9007199616606190.000000 = x
 k=         0    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 

Но когда закомментированная строка ( т.е. //if (argc>1) r = atol(argv[1]);) не закомментирована, она выдает:

 j=     20000    9007199616606190.000000 = x
 k=     17285    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 

, хотя эта строка не должна иметь никакого эффекта, поскольку argc>1 имеет значение false.Кто-нибудь получил правдоподобное объяснение этой проблемы?Воспроизводится ли он на любых других системах?

 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 int main(int argc, char *argv[]) {
   int  j, k, m=10000;
   double r=31443101, jroot=sqrt(83), x;
   //if (argc>1) r = atol(argv[1]);
   x = r * r * jroot;
   j = m*(x-floor(x));
   k = floor(m*(x-floor(x)));
   printf ("j= %9d   %24.6f = x\n", j, x);
   printf ("k= %9d   %24.6f = [x]\n", k, floor(x));
   printf ("r= %9.0f   %24.6f = m*(x-[x]) \n", r, m*(x-floor(x))); 
   return 0;
 }

Обратите внимание, тестовая система = система AMD Athlon 64 5200+ с Linux 2.6.35.14-96.fc14.i686 ( т.е. , загружено дозапустить 32-битную ОС на 64-битном HW) с gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4)

Обновление - Несколько часов назад яопубликовал комментарий о том, что код, сгенерированный с оператором if и без него, отличается только смещениями стека и некоторым пропущенным кодом.Теперь я нахожу, что этот комментарий был не совсем правильным; то есть это верно для неоптимизированного кода, но не верно для кода -O3, который я выполнил.

Влияние переключателя оптимизации на проблему:

  • -O0: обе версии программы работают нормально
  • -O2 или -O3: в версии с комментарием есть ошибка, как указано выше, где j=20000 и k=17285
  • -O1:Версия с комментариями имеет j=20000 (ошибка) и k=0 (ОК)

В любом случае, если посмотреть на списки кодов -O3 -S, два случая отличаются в основном пропущенным кодом if истек смещается до строки, предшествующей call floor, и в этот момент код с-if имеет на fstpl больше, чем код без-if:

    ...  ;; code without comment:
fmul    %st, %st(1)
fxch    %st(1)
fstpl   (%esp)
fxch    %st(1)
fstpl   48(%esp)
fstpl   32(%esp)
call    floor
movl    $.LC2, (%esp)
fnstcw  86(%esp)
movzwl  86(%esp), %eax
    ...
    ...  ;; versus code with comment:
fmul    %st, %st(1)
fxch    %st(1)
fstpl   (%esp)
fxch    %st(1)
fstpl   48(%esp)
fstpl   32(%esp)
fstpl   64(%esp)
call    floor
movl    $.LC3, (%esp)
fnstcw  102(%esp)
movzwl  102(%esp), %eax
    ...

Я не выяснил причинуразница.

Ответы [ 4 ]

3 голосов
/ 24 ноября 2011

Не дублируется в моей системе, Win7 работает с CygWin с gcc 4.3.4.Как с оператором if, так и без него значение j устанавливается равным нулю, а не 20K.

Мое единственное предложение - использовать gcc -S, чтобы взглянуть на вывод ассемблера.Мы надеемся, что это скажет вам, что происходит не так.

В частности, сгенерируйте вывод ассемблера в два отдельных файла, по одному для рабочего и нерабочего варианта, а затем vgrep их (перебирайте их рядом), чтобы попробовать ивыясните разницу.


Кстати, это серьезный сбой в вашей среде.С m, равным 10000, это означает, что x - floor(x) должно быть равно 2. Я не могу представить себе ни одного действительного числа, где бы это было так: -)

2 голосов
/ 24 ноября 2011

Я думаю, что есть две причины, по которым эта строка может иметь эффект:

  • Без этой строки значения всех этих переменных могут быть (и, IMHO, наиболее вероятно, ) определяется во время компиляции;с этой линией вычисления должны выполняться во время выполнения.Но очевидно, что предварительно вычисленные значения компилятора должны быть такими же, как значения, вычисленные во время выполнения, и я склонен игнорировать это как фактическую причину различного наблюдаемого поведения.(Хотя это, безусловно, выглядело бы как огромная разница в выходных данных ассемблера!)
  • На многих машинах арифметика с плавающей запятой выполняется с использованием большего количества битов в промежуточных значениях, чем может быть сохранено с двойной точностьючисло с плавающей точкой.Ваша вторая версия, создав два различных кодовых пути для установки x, в основном ограничивает x тем, что может быть сохранено в числе с плавающей запятой двойной точности, тогда как ваша первая версия может разрешатьПервоначально рассчитанное значение для x должно быть доступно как промежуточное значение с дополнительными битами при вычислении последующих значений.(Это может быть в случае, если все эти значения вычисляются во время компиляции или во время выполнения.)
2 голосов
/ 24 ноября 2011

Причина, по которой раскомментирование этой строки может повлиять на результат, заключается в том, что без этой строки компилятор может видеть, что r и jroot не могут измениться после инициализации, поэтому он может вычислить x привремя компиляции, а не время выполнения.Если строка не закомментирована, r может измениться, поэтому вычисление x должно быть отложено до времени выполнения, что может привести к тому, что оно будет выполнено с другой точностью (особенно если используется математика с плавающей запятой 387).

Вы можете попробовать использовать -mfpmath=sse -march=native, чтобы использовать модуль SSE для вычислений с плавающей запятой, который не демонстрирует избыточную точность;или вы можете попробовать использовать переключатель -ffloat-store.

Ваше вычитание x - floor(x) показывает катастрофическое аннулирование - это коренная причина проблемы что-то, чего следует избегать;).

0 голосов
/ 24 ноября 2011

РЕДАКТИРОВАНИЕ:

Я также не вижу разницы, когда я компилирую ваш код на моем компьютере, используя -O0, -O1, -O2 и -O3.

AMD Phenom Quad 64немного.gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3

Я также пробовал clang (llvm) из версии 3.0 с одинаковыми результатами и без них.

Я согласен, что компилятор может предварительновычисляя все без строки if, вы наверняка увидите это в выходных данных сборки.

С плавающей точкой и C может быть неприятно, много вещей, которые нужно знать, чтобы заставить его действительно работать.Преобразование int в двойное преобразование хорошо для точности (библиотеки c в компиляторе, даже если известно, что fpu хорош, имеют проблемы, и библиотека компиляторов C, которую он использует, и библиотека C, скомпилированная или используемая вашей программой, может / будет)отличаются), но int to / from float - это то место, где FPU, как правило, имеют свои ошибки (я думаю, я видел, что упомянуто с TestFloat или где-то в этом роде).Попробуйте запустить TestFloat в вашей системе, чтобы проверить, хорош ли ваш FPU.Между известной ошибкой с плавающей запятой Pentium и PentiumIV и более поздними днями большинство процессоров имели ошибки с плавающей запятой, у меня был твердый Pentium III, но у меня не получился Pentium IV.Я редко использую плавающую точку, поэтому не беспокойтесь о тестировании моих систем.

Игра с оптимизацией изменила ваши результаты в соответствии с вашими правками, так что это, скорее всего, проблема gcc или комбинация вашего кода и gcc (а не проблема аппаратного fpu).Затем попробуйте другую версию gcc на том же компьютере.4.4.x вместо 4.5.x например.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...