11

IEEE754 标准定义了两类 NaN,静默 NaN,QNaN,和信令 NaN,SNaN。当 SNaN 加载到浮点寄存器中时,浮点单元会引发异常。

QNaN 可通过NaNMath. 该常数的定义是:

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

4

5 回答 5

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}
于 2013-04-27T13:59:41.403 回答
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常量的代码的所有扩展中。

于 2013-04-27T08:29:16.503 回答
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;
于 2013-04-27T08:54:45.833 回答
0

我使用一个函数:

Function UndefinedFloat : double
Begin
  Result := Nan
End;

This then works 
Var 
  MyFloat : double;

Begin
  MyFloat := UndefinedFloat;
于 2013-04-27T11:19:11.960 回答
0

这是一种相当肮脏的方法,它为消费者提供了非常干净的代码。

unit uSNaN;

interface

const
  SNaN: Double=0.0;//SNaN value assigned during initialization

implementation

initialization
  PInt64(@SNaN)^ := $7FF7FFFFFFFFFFFF;

end.

我期待链接器放入SNaN可执行文件的只读段,但似乎没有这样做。无论如何,即使是这样,您也可以VirtualProtect在任务期间使用来解决这个问题。

于 2013-04-27T13:36:18.070 回答