Разница в арифметике с плавающей запятой между x86 и x64

Я наткнулся на разницу в способах создания арифметики с плавающей запятой между сборками MS VS 2010 для x86 и x64 (оба выполнялись на одной 64-битной машине).

Это приведенный пример кода:

float a = 50.0f;
float b = 65.0f;
float c =  1.3f;
float d = a*c;
bool bLarger1 = d<b;
bool bLarger2 = (a*c)<b;

Логический bLarger1 всегда false (d устанавливается в 65.0 в обеих строках). Переменная bLarger2 ложна для x64, но true для x86!

Мне хорошо известно о арифметике с плавающей запятой и эффектах округления. Я также знаю, что 32 бит иногда использует разные инструкции для плавающих операций, чем 64-битные сборки. Но в этом случае мне не хватает информации.

Почему существует расхождение между bLarger1 и bLarger2 на первом месте? Почему он присутствует только в 32-битной сборке?

Left: x86, Right: x64

Ответы

Ответ 1

Проблема зависит от этого выражения:

bool bLarger2 = (a*c)<b;

Я просмотрел код, созданный в VS2008, не имея VS2010. Для 64-битного кода:

000000013FD51100  movss       xmm1,dword ptr [a] 
000000013FD51106  mulss       xmm1,dword ptr [c] 
000000013FD5110C  movss       xmm0,dword ptr [b] 
000000013FD51112  comiss      xmm0,xmm1 

Для 32-битного кода:

00FC14DC  fld         dword ptr [a] 
00FC14DF  fmul        dword ptr [c] 
00FC14E2  fld         dword ptr [b] 
00FC14E5  fcompp           

Итак, до 32 бит вычисление выполняется в блоке x87, а под 64 бит выполняется блоком x64.

И здесь разница заключается в том, что все операции x87 выполняются до более высокой, чем одинарная. По умолчанию вычисления выполняются с двойной точностью. С другой стороны, операции SSE-блока являются чистыми вычислениями с одной точностью.

Вы можете убедить 32-разрядную единицу выполнить все вычисления с точностью до одной точности:

_controlfp(_PC_24, _MCW_PC);

Когда вы добавляете это в свою 32-битную программу, вы обнаружите, что логические значения равны false.

Существует принципиальное различие в том, как работают модули с плавающей точкой x87 и SSE. Блок x87 использует одни и те же инструкции для одиночных и двойных типов точности. Данные загружаются в регистры в стеке x90 FPU, и эти регистры всегда имеют длину в 10 байт. Вы можете управлять точностью с помощью управляющего слова с плавающей запятой. Но инструкции, которые компилятор пишет, не знают этого состояния.

С другой стороны, модуль SSE использует разные инструкции для операций с одинарной и двойной точностью. Это означает, что компилятор может испускать код, который полностью контролирует точность вычисления.

Итак, единица x87 - плохой парень. Возможно, вы попытаетесь убедить своего компилятора испустить инструкции SSE даже для 32-битных целей. И, конечно, когда я скомпилировал ваш код под VS2013, я обнаружил, что как 32, так и 64-битные цели выбрали инструкции SSE.

Ответ 2

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

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

У меня нет определенного источника ответа, но посмотрите эту страницу или это.