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?
Unlike all other possible values in JavaScript, it is not possible to use the equality operators (== and ===) to compare a value against NaN to determine whether the value is NaN or not, because both NaN == NaN and NaN === NaN evaluate to false . The isNaN() function provides a convenient equality check against NaN .
Overview. In C#, the Double. NaN field represents a value that is not a number. It is constant.
You can accomplish the same task, of replacing the NaN values with zeros, by using NumPy: For our example, you can use the following code to perform the replacement: As before, the two NaN values became 0’s: For the first two cases, you only had a single column in the dataset. But what if your DataFrame contains multiple columns?
Check this out: To remain mathematically correct, VB.NET gives you the answer NaN (Not a Number) for some calculations such as 0 / 0. VB.NET can also tell the difference between positive infinity and negative infinity: "negative infinity.")
In addition to PositiveInfinity and NegativeInfinity, VB.NET also provides Epsilon, the smallest positive Double value greater than zero. Keep in mind that all of these new capabilities of VB.NET are only available with floating point (Double or Single) data types.
To remain mathematically correct, VB.NET gives you the answer NaN (Not a Number) for some calculations such as 0 / 0. VB.NET can also tell the difference between positive infinity and negative infinity: "negative infinity.")
First of all, your Delphi program does not behave as you describe, at least on the Delphi version readily available to me, XE7. When your program is run, an invalid operation floating point exception is raised. I'm going to assume that you have actually masked floating point exceptions.
Update: It turns out that at some time between XE7 and 10.3, Delphi 32 bit codegen switched from fcom
to fucom
which explains why XE7 sets the IA floating point exception, but 10.3 does not.
Your Delphi code is very far from minimal. Let's try to make a truly minimal example. And let's look at other comparison operators.
{$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.
Under 32 bit in XE7, this outputs
TRUE TRUE FALSE FALSE TRUE FALSE
Under 32 bit in 10.3.3 (and 10.4.1 as you report in a comment below), this outputs
TRUE TRUE TRUE TRUE FALSE TRUE
Under 64 bit in XE7 and 10.3.3 (and 10.4.1 as your report), this outputs
FALSE FALSE FALSE FALSE FALSE TRUE
The 64 bit output is correct. The 32 bit output for both variants are incorrect. This we we can see by referring to What is the rationale for all comparisons returning false for IEEE754 NaN values?
all comparisons with the operators ==, <=, >=, <, > where one or both values is NaN returns false, contrary to the behaviour of all other values.
For your 32 bit Delphi code, you will need to workaround this bug and include special case code whenever it needs to handle such comparisons. Unless of course, by some happy chance, that you are using 10.4 and it already fixes the issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With