IEEE754标准定义了两类NaN,即安静的NaN,QNaN和信号NaN,SNaN。当SNaN加载到浮点寄存器中时,浮点单元会引发异常。
QNaN可通过在NaN
中声明的名为Math
的常量来获取Delphi代码。该常量的定义是:
const
NaN = 0.0 / 0.0;
我希望能够使用类似的东西来声明一个信号NaN的常量,但还没有找到办法做到这一点。
天真地你可以写下这段代码:
function SNaN: Double;
begin
PInt64(@Result)^ := $7FF7FFFFFFFFFFFF;//this bit pattern specifies an SNaN
end;
但浮点返回值的ABI意味着SNaN被加载到浮点寄存器中,以便可以返回它。当然,这导致了一个例外,而这个例外却违背了目的。
然后你被引导编写这样的代码:
procedure SetToSNaN(out D: Double);
begin
PInt64(@D)^ := $7FF7FFFFFFFFFFFF;
end;
现在,这有效,但非常不方便。假设您需要将SNaN传递给另一个函数。理想情况下你想写:
Foo(SNaN)
但你必须这样做:
var
SNaN: Double;
....
SetToSNaN(SNaN);
Foo(SNaN);
所以,在积累之后,这就是问题。
有没有办法写x := SNaN
并为浮点变量x
分配一个信号NaN的值?
答案 0 :(得分:8)
此声明在编译时解决了它:
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double absolute iNaN;
编译器仍然将SNaN
视为常量。
尝试为SNaN
分配值会产生编译时错误: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"
如果你有编译器指令$WRITEABLECONSTS ON
(或$J+
),可以暂时关闭它以确保不改变SNaN
。
{$IFOPT J+}
{$DEFINE UNDEFWRITEABLECONSTANTS}
{$J-}
{$ENDIF}
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double ABSOLUTE iNaN;
{$IFDEF UNDEFWRITEABLECONSTANTS}
{$J+}
{$ENDIF}
答案 1 :(得分:4)
您可以内联函数:
function SNaN: Double; inline;
begin
PInt64(@Result)^ := $7FF7FFFFFFFFFFFF;
end;
但这取决于优化和编译器的情绪。
我看到一些函数没有内联,没有从上下文中有任何清楚的理解。我不喜欢依靠内联。
我会做的更好,哪些适用于所有版本的Delphi,是使用全局变量:
var
SNaN: double;
然后将其设置在单位的initialization
块中:
const
SNaN64 = $7FF7FFFFFFFFFFFF;
initialization
PInt64(@SNaN)^ := SNaN64;
end.
然后您就可以将SNaN
用作常规常量。也就是说,您可以按预期编写代码:
var test: double;
...
test := SNaN;
在IDE调试器中,它将显示为“test = + NAN”,这是预期的结果,我猜想。
请注意,使用此SNaN
会在读入FPU堆栈时引发异常(例如if test=0 then
),因此您必须检查二进制级别的值...这就是为什么我定义了一个SNaN64
常量,顺便说一句,它会生成非常快的代码。
toto := SNaN;
if PInt64(@toto)^<>SNaN64 then // will run and work as expected
DoubleToString(toto);
if toto<>SNaN then // will raise an EInvalidOp at runtime
DoubleToString(toto);
您可以通过更改x87异常寄存器来更改此行为:
backup := Set8087CW($133F);
try
..
finally
Set8087CW(backup);
end;
我想这是为你的程序全局设置的,在代码的所有代码中都必须处理这个SNaN
常量。
答案 2 :(得分:4)
这是另一种解决方法:
type
TFakeRecord = record
case Byte of
0: (SNaN: Double);
1: (i: Int64);
end;
const
IEEE754: TFakeRecord = ( i: $7FF7FFFFFFFFFFFF);
调试器将IEEE754.SNaN显示为+ NAN,但是当您访问它时,您仍将获得浮点异常。解决方法可能是:
type
ISet8087CW = interface
end;
TISet8087CW = class(TInterfacedObject, ISet8087CW)
protected
OldCW: Word;
public
constructor Create(const NewCW: Word);
destructor Destroy; override;
end;
TIEEE754 = record
case Byte of
0: (SNaN: Double);
1: (i: Int64);
end;
const
IEEE754: TIEEE754 = ( i: $7FF7FFFFFFFFFFFF);
{ TISet8087CW }
constructor TISet8087CW.Create(const NewCW: Word);
begin
OldCW := Get8087CW;
Set8087CW(NewCW);
inherited Create;
end;
destructor TISet8087CW.Destroy;
begin
Set8087CW(OldCW);
inherited;
end;
procedure TForm6.Button4Click(Sender: TObject);
var
CW: ISet8087CW;
begin
CW := TISet8087CW.Create($133F);
Memo1.Lines.Add(Format('SNaN: %f', [IEEE754.SNaN]));
end;
答案 3 :(得分:0)
我使用了一个函数:
Function UndefinedFloat : double
Begin
Result := Nan
End;
This then works
Var
MyFloat : double;
Begin
MyFloat := UndefinedFloat;
答案 4 :(得分:0)
这是一种相当脏的方法,可以为消费者提供非常干净的代码。
unit uSNaN;
interface
const
SNaN: Double=0.0;//SNaN value assigned during initialization
implementation
initialization
PInt64(@SNaN)^ := $7FF7FFFFFFFFFFFF;
end.
我希望链接器将SNaN
放在可执行文件的只读段中,但似乎不会这样做。在任何情况下,即使它确实如此,您也可以使用VirtualProtect
来解决分配期间的问题。