【问题标题】:Difference in floating point arithmetics between x86 and x64x86 和 x64 浮点运算的区别
【发布时间】:2014-05-07 18:19:55
【问题描述】:

我偶然发现了 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 为 false,但对于 x86 为 true!

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

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

【问题讨论】:

  • 我的猜测是 x86 版本为此使用 FPU 寄存器,而 x64 使用 SSE 寄存器。但是您可能需要查看 IL 代码以及机器代码。
  • 当然 x86 使用 x87 单元,x64 使用 SSE 单元。但这并不能真正解释差异。他们都应该得到相同的答案。 @Oliver 你能展示你是如何编译代码的吗,因为我的复制尝试失败了。对于我来说,x86 和 x64 的两个布尔值都是 false
  • 这将是bLarger2 的表达式中处理 a*c 的方式。我想它会是一个浮点数和另一个双倍数,或者类似的东西
  • 真正的问题是为什么这令人惊讶,完全清楚浮点并不精确。根据编译器、编译器选项等不同,结果可能会有所不同。
  • @PaulMcKenzie 虽然浮点运算不能完全表示所有实数值,但它是可重复且定义明确的。希望不同编译器之间的一致性并非没有道理。

标签: c++ c visual-studio-2010 floating-point 64-bit


【解决方案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 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 指令。

【讨论】:

  • 很有趣,所以我假设计算 bLarger1 和 bLarger2 的方式之间的差异是 ALU 的精度。对于 bLarger2,精度较高的结果将保存在 ALU 中进行比较,而 bLarger1 将首先加载单精度值并进行比较。
  • @OliverZendel 正确
  • 嗯,更改“启用增强指令集”和“浮点模型”都不会导致不同的行为(VS2010 x86); _controlfp 可以解决问题,但我仍然很好奇是否存在导致相同行为的编译器设置本身
  • @OliverZendel 没有编译器设置会改变控制字。这是一个运行时属性,因此必须在运行时进行管理。这是一个完全雷区的 FWIW,带有左右模块和中心螺丝与控制字。使用/arch:SSE2 编译,您将避免x87 的所有痛苦。但是你的程序只能在带有 SSE2 单元的机器上运行。
  • 实际上我什至不确定 /arch:SSE2 是否一定会完成它。无论如何,我认为我们至少了解了这种行为的原因。
【解决方案2】:

浮点运算总是不精确的,比较两个如此接近(或相等)的浮点数几乎永远不会返回正确的输出。

浮点数在 32 位和 64 位机器上的存储和处理方式不同(cmets 也建议)。如果我没记错的话,在 VC 中,32 位浮点数保存在堆栈中并由 FPU(浮点单元)处理它们,而 64 位机器上的浮点数可以存储在专用寄存器(SSE)中,并使用 CPU 中的其他单元计算。

我的答案没有确切的来源,但请看this pagethis.

【讨论】:

  • 存储没有区别。这些是 IEEE754 浮点数。一种标准格式。
  • @DavidHeffernan,它们的存储方式相同,但我想说的是不同的位置。
  • 但浮点数不一定存储在堆栈中。 x87 单元有自己的寄存器堆栈,包含 8 个扩展精度浮点寄存器 ST(0) 到 ST(7)。它们肯定是专门的寄存器。 SSE 单元有自己的专用寄存器。
猜你喜欢
  • 1970-01-01
  • 2021-09-26
  • 2013-05-15
  • 2011-11-29
  • 1970-01-01
  • 1970-01-01
  • 2015-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多