delphi - Win32 和 Win64 中 exAllArithmeticExceptions 的结果不一致

标签 delphi delphi-10-seattle

我的一位同事发现 Delphi 编译的 Win32 和 Win64 代码在处理 NaN 的方式上存在差异。以下面的代码为例。当以 32 位编译时,我们没有收到任何消息,但当以 64 位编译时,我们得到两个比较都返回 true。

program TestNaNs;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Math;

var
  nanDouble: Double;
  zereDouble: Double;
  nanSingle: Single;
  zeroSingle: Single;
begin
  SetExceptionMask(exAllArithmeticExceptions);
  nanSingle := NaN;
  zeroSingle := 0.0;
  if nanSingle <> zeroSingle then
    WriteLn('nanSingle <> zeroSingle');

  nanDouble := NaN;
  zereDouble := 0.0;
  if nanDouble <> zereDouble then
    WriteLn('nanDouble <> zeroDouble');

  ReadLn;
end.

我对 IEEE 标准的理解是 <> 应该返回 true,但所有其他操作应该返回 false。所以在这种情况下,看起来 64 位版本是正确的,而 32 位版本是错误的。两者生成的代码与64位版本生成的SSE代码有很大不同。

对于 32 位:

TestNaNs.dpr.21: if nanSingle <> zeroSingle then
0041A552 D905E01E4200     fld dword ptr [$00421ee0]
0041A558 D81DE41E4200     fcomp dword ptr [$00421ee4]
0041A55E 9B               wait 
0041A55F DFE0             fstsw ax
0041A561 9E               sahf 
0041A562 7419             jz $0041a57d

对于 64 位:

TestNaNs.dpr.21: if nanSingle <> zeroSingle then
000000000042764E F3480F5A05C9ED0000 cvtss2sd xmm0,qword ptr [rel $0000edc9]
0000000000427657 F3480F5A0DC4ED0000 cvtss2sd xmm1,qword ptr [rel $0000edc4]
0000000000427660 660F2EC1         ucomisd xmm0,xmm1
0000000000427664 7A02             jp Project63 + $68
0000000000427666 7420             jz Project63 + $88

我的问题是这样的。这是 Delphi 编译器的问题还是 Intel CPU 的问题?

最佳答案

IEEE 754 标准定义了浮点计算的算术格式、运算、舍入规则、异常(exception)等。 Delphi 编译器在可用的硬件单元之上实现浮点运算。对于 32 位 Windows 编译器,这是 x87 单元,对于 64 位 Windows 编译器,这是 SSE 单元。这两个硬件单元均符合 IEEE 754 标准。

您观察到的差异出现在语言实现级别。让我们更详细地看看这两个版本。

32位Windows编译器

比较语句编译为:

TestNaNs.dpr.19: if nanDouble <> zeroDouble then
0041C4C8 DD05C03E4200     fld qword ptr [$00423ec0]
0041C4CE DC1DC83E4200     fcomp qword ptr [$00423ec8]
0041C4D4 9B               wait 
0041C4D5 DFE0             fstsw ax
0041C4D7 9E               sahf 
0041C4D8 7419             jz $0041c4f3

英特尔软件开发人员手册指出,无序比较由标志 C3、C2 和 C0 设置为 1 表示。完整表格如下:

Condition       C3  C2  C0
ST(0) > Source  0   0   0
ST(0) < Source  0   0   1
ST(0) = Source  1   0   0
Unordered       1   1   1

当您在调试器下检查 FPU 时,您可以看到情况就是如此。

0041C4D5 DFE0             fstsw ax
0041C4D7 9E               sahf 
0041C4D8 7419             jz $0041c4f3

这会将 FPU 状态寄存器中的各个位传输到 CPU 标志中,有关标志所在位置的准确详细信息,请参阅手册。如果设置了 ZF,则会进行分支。 ZF 的值来自 C3 FPU 标志,从上表中读取,该标志是为无序情况设置的。

事实上,整个分支代码可以用伪代码表示为:

jump if C3 = 1

因此,查看上表,很明显,如果其中一个操作数是 NaN,则任何浮点相等比较都会计算为相等。

64位Windows编译器

比较语句编译为:

TestNaNs.dpr.19: if nanDouble <> zeroDouble then
0000000000428EB8 F20F100548E50000 movsd xmm0,qword ptr [rel $0000e548]
0000000000428EC0 660F2E0548E50000 ucomisd xmm0,qword ptr [rel $0000e548]
0000000000428EC8 7A02             jp TestNaNs + $5C
0000000000428ECA 7420             jz TestNaNs + $7C

比较由ucomisd指令执行。手册给出了这个伪代码:

RESULT ← UnorderedCompare(SRC1[63:0] <> SRC2[63:0]) {
(* Set EFLAGS *)
CASE (RESULT) OF
  GREATER_THAN:   ZF, PF, CF ← 000;
  LESS_THAN:      ZF, PF, CF ← 001;
  EQUAL:          ZF, PF, CF ← 100;
  UNORDERED:      ZF, PF, CF ← 111;
ESAC;
OF, AF, SF ← 0;

请注意,在此指令中,ZF、PF 和 CF 标志与 x87 单元上的 C3、C2 和 C0 标志完全相同。

分支由以下代码处理:

0000000000428EC8 7A02             jp TestNaNs + $5C
0000000000428ECA 7420             jz TestNaNs + $7C

请注意,首先测试奇偶校验标志 PF(jp 指令),然后测试零标志 ZF(jz 指令)。因此,编译器发出了代码来处理无序情况(即操作数之一是 NaN)。这首先由 jp 处理。一旦处理完毕,编译器就会检查零标志 ZF,当且仅当两个操作数相等时才设置该标志(因为 NaN 已被处理)。

结论

不同的行为是由于不同的编译器在如何实现比较运算符方面采取了不同的选择。在这两种情况下,硬件均符合 IEEE 754 标准,并且完全能够按照标准的规定比较 NaN。

我最好的猜测是,32 位编译器的决定是很久以前做出的。其中一些决定值得怀疑。在我看来,与 NaN 操作数的相等比较应该评估不等于,而不管其他操作数如何。通过保持向后兼容性的愿望感受到的历史的重量意味着这些有问题的决定从未得到解决。

最近,当 64 位编译器创建时,Embarcadero 工程师决定纠正其中一些错误。他们大概觉得突破新架构让他们可以自由地这样做。

在理想情况下,通过设置编译器开关,可以将 32 位编译器配置为与 64 位编译器具有相同的行为方式。

关于delphi - Win32 和 Win64 中 exAllArithmeticExceptions 的结果不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46897007/

相关文章:

database - 如何从 Delphi 访问 Visual FoxPro 表?

delphi - Delphi 有没有好的类似 Pascal 的脚本语言?

delphi - 自定义 GridPanel ControlItems 问题

delphi - 如何退出就地编辑器并在 Delphi 中处理按钮?

delphi - SocketStream发送十六进制值

Delphi:需要时提示 UAC 提升

Delphi Indy 附件不工作

delphi - 在运行时创建 TActivityIndi​​cator 时出错

delphi - 如果未设置属性,则生成编译器错误