Двойное умножение отличается от времени компиляции и времени выполнения на 32-битной платформе

Я компилирую и запускаю следующую программу на 32- и 64-разрядных платформах:

int main() { double y = 8.34214e08; double z = 1.25823e45; return y * z == 8.34214e08 * 1.25823e45; } 

В то время как в 64-битном результате ожидается ожидаемый (значения одинаковы, а код выхода не равен нулю) в 32 бит, кажется, что существует небольшая разница между значением, вычисленным во время компиляции, правой стороной сравнения, а слева вычисленная во время выполнения.

Это ошибка в компиляторе или есть логическое объяснение?

EDIT: это отличается от того, почему сравнение double и float приводит к неожиданному результату? потому что здесь все значения двойные.

IEEE-754 позволяет проводить промежуточные вычисления с большей точностью (акцент мой).

(IEEE-754: 2008) «Языковой стандарт также должен определять и требовать реализации, чтобы предоставлять атрибуты, которые позволяют и запрещают оптимизацию изменения стоимости отдельно или коллективно для блока. Эти оптимизации могут включать, но не ограничиваются: […] Использование более широких промежуточных результатов в оценке выражений . ”

В вашем случае, например, на IA-32, двойные значения могут быть сохранены в регистрах FPU x87 с большей точностью (80-бит вместо 64). Таким образом, вы фактически сравниваете умножение, выполняемое с двойной точностью, с умножением на двойную расширенную точность.

Например, на x64, где результат равен 1 (x90 FPU не используется, поскольку вместо него используется SSE), добавление опции gcc -mfpmath=387 для использования x87 приводит к изменению результата на 0 на моей машине.

И если вам интересно, разрешено ли это C, это:

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

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

То, что происходит здесь, по всей вероятности, связано с тем, что умножение выполняется на двух разных «платформах»: один раз вашим кодом и один раз компилятором, который может иметь другую точность. Это происходит с большинством компиляторов .

Ваша программа, вероятно, сработает, если вы скомпилируете ее с теми же параметрами, которые были использованы для компиляции компилятора (предполагая, что компилятор был скомпилирован сам по себе). Но это не означает, что вы получите правильный результат; вы получите ту же ошибку точности, которую получает компилятор.

(Кроме того, я предполагаю, что компилятор выполняет прямое умножение, а код распознавания, распознающий поплавки, не входит в уравнение. Это может быть принятие желаемого за действительное с моей стороны).

тестирование

 Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib64/gcc/x86_64-suse-linux/4.8/lto-wrapper Target: x86_64-suse-linux Configured with: ../configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man --libdir=/usr/lib64 --libexecdir=/usr/lib64 --enable-languages=c,c++,objc,fortran,obj-c++,java,ada --enable-checking=release --with-gxx-include-dir=/usr/include/c++/4.8 --enable-ssp --disable-libssp --disable-plugin --with-bugurl=http://bugs.opensuse.org/ --with-pkgversion='SUSE Linux' --disable-libgcj --disable-libmudflap --with-slibdir=/lib64 --with-system-zlib --enable-__cxa_atexit --enable-libstdcxx-allocator=new --disable-libstdcxx-pch --enable-version-specific-runtime-libs --enable-linker-build-id --enable-linux-futex --program-suffix=-4.8 --without-system-libunwind --with-arch-32=i586 --with-tune=generic --build=x86_64-suse-linux --host=x86_64-suse-linux Thread model: posix gcc version 4.8.3 20141208 [gcc-4_8-branch revision 218481] (SUSE Linux) #include  int main() { double y = 8.34214e08; double z = 1.25823e45; return printf("%s\n", y * z == 8.34214e08 * 1.25823e45 ? "Equal" : "NOT equal!"); } 

Forcing -O0, чтобы избежать компилятора от оптимизации всего кода (спасибо @markgz!), Мы получаем

$ gcc -m32 -O0 -o float float.c && ./float НЕ равно! $ gcc -m32 -frounding-math -O0 -o float float.c && ./float Равный

Для записи, поскольку вы добрались до меня :-),

-frounding-математический

Отключить преобразования и оптимизации, которые предполагают поведение округления с плавающей запятой по умолчанию. Это округление до нуля для всех преобразований с плавающей точкой в ​​целые числа и от округлых до ближайших для всех других арифметических усечений. Эта опция должна быть указана для программ, которые изменяют режим округления FP динамически или могут быть выполнены с использованием режима округления по умолчанию. Этот параметр отключает постоянную фальцовку выражений с плавающей точкой во время компиляции (на которые может влиять режим округления) и арифметические преобразования, которые являются небезопасными при наличии зависящих от знака режимов округления.

По умолчанию используется значение -fno-rounding-math.

Расчеты с плавающей запятой, выполняемые во время компиляции, часто происходят с большей точностью, чем double использование во время выполнения. Кроме того, C может выполнять промежуточные промежуточные вычисления во время выполнения при более высокой long double точности. Объясните свое неравенство. Подробнее см. FLT_EVAL_METHOD .

  volatile double y = 8.34214e08; volatile double z = 1.25823e45; volatile double yz = 8.34214e08 * 1.25823e45; printf("%.20e\n", y); printf("%.20e\n", z); printf("%.20e\n", yz); printf("%.20Le\n", (long double) y*z); printf("%.20Le\n", (long double) 8.34214e08 * 1.25823e45); 8.34214000000000000000e+08 1.25822999999999992531e+45 // 3 different products! 1.04963308121999993395e+54 1.04963308121999993769e+54 1.04963308122000000000e+54 

Ваши результаты могут незначительно отличаться.