I am porting C code to Delphi and find an issue in the way the compilers (Delphi 10.4.1 and MSVC2019, both targeting x32 platform) handle comparison of +NAN to zero. Both compilers use IEEE754 representation for double floating point values. I found the issue because the C-Code I port to Delphi is delivered with a bunch of data to validate the code correctness.
The original source code is complex but I was able to produce a minimal reproducible application in both Delphi and C.
C-Code:
#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
}
Delphi code:
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.
I have marked the lines with are executed after a comparison. Delphi evaluate (lon12 >= 0) to TRUE when lon12 variable equal +NAN. MSVC evaluate (lon12 >= 0) to FALSE when lon12 variable equal +NAN.
lonsign has different values in C and Delphi.
AngRound receiving +NAN as argument return different values.
Final value of lon12 is (fatally) different.
Machine code generated by the compilers are different:
Delphi generated machine code:
MSVC2019 generated machine code:
The comparison result seems more logical in Delphi: (lon12 >= 0) is TRUE when lon12 is +NAN. Does this means the bug is in MSVC2019 compiler? Should I consider the test data set of the original C-Code carry in error?