如何使信令NaN易于使用?

时间:2013-04-27 08:00:27

标签: delphi

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的值?

5 个答案:

答案 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来解决分配期间的问题。