x86 和 x64 浮点运算的区别

我偶然发现了 MS VS 2010 为 x86 和 x64 构建的浮点运算方式的差异(两者都在同一 64 位机器上执行).

这是一个简化的代码示例:

float a = 50.0f;浮动 b = 65.0f;浮动 c = 1.3f;浮动 d = a*c;bool bLarger1 = d

布尔值 bLarger1 始终为 false(在两个版本中 d 都设置为 65.0).变量 bLarger2 对于 x64 为 false,但对于 x86 为 true!

我非常了解浮点运算和发生的舍入效应.我也知道 32 位有时使用与 64 位构建不同的指令进行浮动操作.但在这种情况下,我遗漏了一些信息.

首先为什么 bLarger1 和 bLarger2 之间存在差异?为什么它只存在于 32 位版本上?

解决方案

问题在于这个表达式:

bool bLarger2 = (a*c)

我查看了在VS2008下生成的代码,手头没有VS2010.对于 64 位,代码为:

<上一页>000000013FD51100 movss xmm1,dword ptr [a]000000013FD51106 mulss xmm1,dword ptr [c]000000013FD5110C movss xmm0,dword ptr [b]000000013FD51112 commiss xmm0,xm??m1

32 位的代码是:

<上一页>00FC14DC fld dword ptr [a]00FC14DF fmul dword ptr [c]00FC14E2 fld 双字指针 [b]00FC14E5 fcomp

所以32位下是x87单元计算的,64位下是x64单元计算的.

这里的区别在于 x87 操作都执行到高于单精度.默认情况下,计算以双精度执行.另一方面,SSE 单元操作是纯单精度计算.

您可以像这样说服 32 位单元以单精度精度执行所有计算:

_controlfp(_PC_24, _MCW_PC);

当您将它添加到您的 32 位程序时,您会发现布尔值都设置为 false.

x87 和 SSE 浮点单元的工作方式存在根本差异.x87 单元对单精度和双精度类型使用相同的指令.数据加载到 x87 FPU 堆栈中的寄存器中,并且这些寄存器始终是 10 字节英特尔扩展的.您可以使用浮点控制字控制精度.但是编译器写的指令是不知道那个状态的.

另一方面,SSE 单元对单精度和双精度操作使用不同的指令.这意味着编译器可以发出完全控制计算精度的代码.

所以,x87 单元在这里是坏人.您可以尝试说服您的编译器即使针对 32 位目标也发出 SSE 指令.当然,当我在 VS2013 下编译您的代码时,我发现 32 位和 64 位目标都发出 SSE 指令.

I stumbled upon a difference in the way floating point arithmetics are done between MS VS 2010 builds for x86 and x64 (both executed on the same 64 bit machine).

This is a reduced code sample:

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;

The boolean bLarger1 is always false (d is set to 65.0 in both builds). Variable bLarger2 is false for x64 but true for x86!

I am well aware of floating point arithmetics and the rounding effects taking place. I also know that 32 bit sometimes uses different instructions for floating operations than 64 bit builds. But in this case I am missing some information.

Why is there a discrepency between bLarger1 and bLarger2 on the first place? Why is it only present on the 32 bit build?

解决方案

The issue hinges on this expression:

bool bLarger2 = (a*c)<b;

I looked at the code generated under VS2008, not having VS2010 to hand. For 64 bit the code is:

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

For 32 bit the code is:

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

So under 32 bit the calculation is performed in the x87 unit, and under 64 bit it is performed by the x64 unit.

And the difference here is that the x87 operations are all performed to higher than single precision. By default the calculations are performed to double precision. On the other hand the SSE unit operations are pure single precision calculations.

You can persuade the 32 bit unit to perform all calculations to single precision accuracy like this:

_controlfp(_PC_24, _MCW_PC);

When you add that to your 32 bit program you will find that the booleans are both set to false.

There is a fundamental difference in the way that the x87 and SSE floating point units work. The x87 unit uses the same instructions for both single and double precision types. Data is loaded into registers in the x87 FPU stack, and those registers are always 10 byte Intel extended. You can control the precision using the floating point control word. But the instructions that the compiler writes are ignorant of that state.

On the other hand, the SSE unit uses different instructions for operations on single and double precision. Which means that the compiler can emit code that is in full control of the precision of the calculation.

So, the x87 unit is the bad guy here. You can maybe try to persuade your compiler to emit SSE instructions even for 32 bit targets. And certainly when I compiled your code under VS2013 I found that both 32 and 64 bit targets emitted SSE instructions.

相关文章