The IEEE754 standard defines two classes of NaN, the quiet NaN, QNaN, and the signaling NaN, SNaN. When an SNaN is loaded into a floating point register, an exception is raised by the floating point unit.
QNaN is available to Delphi code through the constant named NaN
that is declared in Math
. The definition of that constant is:
const
NaN = 0.0 / 0.0;
I would like to be able to use something similar to declare a constant that is a signaling NaN, but have not yet found a way to do that.
Naively you might write this code:
function SNaN: Double;
begin
PInt64(@Result)^ := $7FF7FFFFFFFFFFFF;//this bit pattern specifies an SNaN
end;
But the ABI for floating point return values means that the SNaN is loaded into a floating point register so that it can be returned. Naturally that leads to an exception which rather defeats the purpose.
So you are then led to writing code like this:
procedure SetToSNaN(out D: Double);
begin
PInt64(@D)^ := $7FF7FFFFFFFFFFFF;
end;
Now, this works, but it's very inconvenient. Suppose you need to pass an SNaN to another function. Ideally you would like to write:
Foo(SNaN)
but instead you have to do this:
var
SNaN: Double;
....
SetToSNaN(SNaN);
Foo(SNaN);
So, after the build-up, here's the question.
Is there any way to write x := SNaN
and have the floating point variable x
assigned a value that is a signaling NaN?
NaN provides development and consulting services for software projects. It is currently a one man business run by Nir Nahum. NaN's core values: Integrity – Always be honest and do what you said you would do, when you said you would do it. Efficiency – Reuse code wherever applicable. Automate tasks as much as possible.
A signaling NaN is represented by any bit pattern between X'7F80 0001' and X'7FBF FFFF' or between X'FF80 0001' and X'FFBF FFFF'. A quiet NaN is represented by any bit pattern between X'7FC0 0000' and X'7FFF FFFF' or between X'FFC0 0000' and X'FFFF FFFF'.
SNaNs are typically used to trap or invoke an exception handler. They must be inserted by software; that is, the processor never generates an SNaN as a result of a floating-point operation. This can be seen from our example where both: float div_0_0 = 0.0f / 0.0f; float sqrt_negative = std::sqrt(-1.0f);
In floating-point calculations, NaN is not the same as infinity, although both are typically handled as special cases in floating-point representations of real numbers as well as in floating-point operations.
This declaration solves it at compile time:
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double absolute iNaN;
The compiler still treats the SNaN
as a constant.
Trying to assign a value to SNaN
will give a compile time error: E2064 Left side cannot be assigned to
.
procedure DoSomething( var d : Double);
begin
d := 2.0;
end;
SNaN := 2.0; // <-- E2064 Left side cannot be assigned to
DoSomething( SNaN); // <--E2197 Constant object cannot be passed as var parameter
WriteLn(Math.IsNaN(SNaN)); // <-- Writes "true"
Should you have the compiler directive $WRITEABLECONSTS ON
(or $J+
), this could be turned off temporarily to ensure not altering SNaN
.
{$IFOPT J+}
{$DEFINE UNDEFWRITEABLECONSTANTS}
{$J-}
{$ENDIF}
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double ABSOLUTE iNaN;
{$IFDEF UNDEFWRITEABLECONSTANTS}
{$J+}
{$ENDIF}
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