Delphi和MSVC不会以相同的方式将+NAN与零进行比较

我正在将 C 代码移植到 Delphi,并发现编译器(Delphi 10.4.1 和 MSVC2019,均针对 x32 平台)处理 +NAN 与零的比较的方式存在问题。两种编译器都使用 IEEE754 表示双浮点值。我发现了这个问题,因为我移植到 Delphi 的 C 代码附带了一堆数据来验证代码的正确性。

原始源代码很复杂,但我能够在 Delphi 和 C 中生成最小的可重现应用程序。

C代码:

#include <stdio.h>
#include <math.h>

double AngRound(double x) {
    const double z = 1 / (double)(16);
    volatile double y;
    if (x == 0) 
        return 0;
    y = fabs(x);
    /* The compiler mustn't "simplify" z - (z - y) to y */
    if (y < z)
        y = z - (z - y);      // <= This line is *NOT* executed
    if (x < 0)
        return -y;
    else
        return y;             // <= This line is executed
}

union {
    double d;
    int bits[2];
} u;


int main()
{
    double lon12;
    double ar;
    int    lonsign;

    // Create a +NAN number IEEE754
    u.bits[0] = 0;
    u.bits[1] = 0x7ff80000;

    lon12    = u.d;                // Debugger shows lon12 is +nan
    if (lon12 >= 0)
        lonsign = 1;
    else
        lonsign = -1;              // <= This line is executed
    // Now lonsign is -1

    ar = AngRound(lon12);
    // Now ar is +nan

    lon12 = lonsign * ar;
    // Now lon12 is +nan
}

德尔福代码:

program NotANumberTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
    TRec = record
       case t : Boolean of
       TRUE:  (d    : Double);
       FALSE: (bits : array [0..1] of Cardinal);
    end;

function AngRound(x : Double) : Double;
const
    z : Double = 1 / Double(16);
var
    y : Double;
begin
    if x = 0 then
        Result := 0
    else begin
        y := abs(x);
        if y < z then
            // The compiler mustn't "simplify" z - (z - y) to y
            y := z - (z - y);           // <= This line is executed
        if x < 0 then
            Result := -y                // <= This line is executed
        else
            Result := y;
    end;
end;

var
    u       : TRec;
    lon12   : Double;
    lonsign : Integer;
    ar      : Double;
begin
    // Create a +NAN number IEEE754
    u.bits[0] := 0;
    u.bits[1] := $7ff80000;

    lon12 := u.d;                       // Debugger shows lon12 is +NAN
    if lon12 >= 0 then
        lonsign := 1                    // <= This line is executed
    else
        lonsign := -1;
    // Now lonsign is +1

    ar := AngRound(lon12);
    // Now ar is -NAN

    lon12 := lonsign * ar;
    // Now lon12 is -NAN
end.

我已经标记了比较后执行的行。当 lon12 变量等于 +NAN 时,Delphi 将 (lon12 >= 0) 评估为 TRUE。当 lon12 变量等于 +NAN 时,MSVC 将 (lon12 >= 0) 评估为 FALSE。

lonsign 在 C 和 Delphi 中有不同的值。

AngRound 接收 +NAN 作为参数返回不同的值。

lon12 的最终值是(致命的)不同的。

编译器生成的机器码是不同的:

Delphi生成的机器码:

MSVC2019生成的机器码:

Delphi 中的比较结果似乎更合乎逻辑:当 lon12 为 +NAN 时,(lon12 >= 0) 为 TRUE。这是否意味着错误在 MSVC2019 编译器中?我应该考虑原始 C-Code 的测试数据集进位错误吗?

回答

首先,您的 Delphi 程序不像您描述的那样运行,至少在我现成的 Delphi 版本 XE7 上。当您的程序运行时,会引发无效操作浮点异常。我将假设您实际上已经屏蔽了浮点异常。

更新:事实证明,在 XE7 和 10.3 之间的某个时间,Delphi 32 位代码生成器从 切换fcomfucom这解释了为什么 XE7 设置 IA 浮点异常,但 10.3 没有。

您的 Delphi 代码远非最小。让我们试着做一个真正最小的例子。让我们看看其他比较运算符。

{$APPTYPE CONSOLE}

uses
  System.Math;

var
  d: Double;
begin
  SetFPUExceptionMask(exAllArithmeticExceptions);
  SetSSEExceptionMask(exAllArithmeticExceptions);
  d := NaN;
  Writeln(d > 0);
  Writeln(d >= 0);
  Writeln(d < 0);
  Writeln(d <= 0);
  Writeln(d = d);
  Writeln(d <> d);
end.

在 XE7 的 32 位下,这输出

真的
真的
错误的
错误的
真的
错误的

在 10.3.3 中的 32 位下(和 10.4.1,正如您在下面的评论中报告的那样),这输出

真的
真的
真的
真的
错误的
真的

在 XE7 和 10.3.3(和 10.4.1 作为您的报告)中的 64 位下,这输出

错误的
错误的
错误的
错误的
错误的
真的

64 位输出是正确的。两种变体的 32 位输出都不正确。我们可以通过参考对于 IEEE754 NaN 值返回 false 的所有比较的基本原理是什么?

与运算符 ==、<=、>=、<、> 的所有比较,其中一个或两个值为 NaN 都返回 false,这与所有其他值的行为相反。

对于您的 32 位 Delphi 代码,您将需要解决此错误并在需要处理此类比较时包含特殊情况代码。当然,除非您有幸使用 10.4 并且它已经解决了问题。

  • @DavidHeffernan: The standard does not specify the interpretation of the sign bit of a NaN, but it does specify its handling even for NaNs. The `copy`, `negate`, and `abs` operations copy, flip, and clear the sign bit. There is even a `copySign` bit to copy it from one operand and paste it onto another and an `isSignMinus` to test it. The `totalOrder` predicate is affected by the sign bit of a NaN.

以上是Delphi和MSVC不会以相同的方式将+NAN与零进行比较的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>