2013-04-27 63 views
10

IEEE754標準定義了兩類NaN,安靜的NaN,QNaN和信令NaN,SNaN。當SNaN被加載到浮點寄存器中時,浮點單元會引發異常。如何使信令NaN容易使用?

QNaN可通過在Math中聲明的常量NaN提供給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分配的值是一個信號喃?

+0

你有沒有嘗試內聯你的第一種方法? – 2013-04-27 08:04:06

+1

@UweRaabe其實我有。我想讓其他人寫出答案,以便他們能得到代表。依靠內聯,我仍然有點不安。如果函數調用不是內聯的,那麼繁榮。 – 2013-04-27 08:21:13

回答

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} 
+0

+1這樣做的竅門,但我會覺得生活在一個變量而不是一個常量中的價值有點令人不安。 – 2013-04-27 14:01:12

+0

給SNaN賦值會給編譯器提供一個錯誤:'E2064左側不能分配給'。 – 2013-04-27 14:12:33

+0

啊,我明白了。確實做得非常好。 – 2013-04-27 18:24:28

4

可以內聯函數:

function SNaN: Double; inline; 
begin 
    PInt64(@Result)^ := $7FF7FFFFFFFFFFFF; 
end; 

但是這將取決於優化編譯器心情。

我看到一些函數沒有內聯,沒有從上下文中得到任何清楚的理解。我不喜歡依靠內聯。

我會做的更好,而這將在德爾福的所有版本,是使用全局變量:

var 
    SNaN: double; 

然後將其設置在單元的initialization塊:

const 
    SNaN64 = $7FF7FFFFFFFFFFFF; 

initialization 
    PInt64(@SNaN)^ := SNaN64; 
end. 

然後,您將可以使用SNaN作爲常規常量。也就是說,隨着預期的您可以編寫代碼:

var test: double; 
... 
    test := SNaN; 

在IDE調試器,它會顯示爲「測試= + 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代碼不變。

+0

是不是引發了一個信號NaN的全部用途? – 2013-04-27 08:48:03

+0

根據我的經驗,如果函數定義在首先**編譯的單元的實現部分的** start **處,函數將始終以內聯方式。 – 2013-04-27 09:19:31

+1

關於測試SNaN,有多個位模式是SNaN。所以你需要更好的測試。 Math中的IsNaN函數是一個很好的起點。 – 2013-04-27 09:22:10

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; 
+0

看起來RAII模式正在德爾菲的土地上起飛。 ;-) – 2013-04-27 13:11:22

+0

+1我很喜歡變體記錄。順便說一句,你知道'Set8087CW'不是線程安全嗎? – 2013-04-27 13:29:20

+0

@DavidHeffernan是的,Delphi將Default8087CW變量中的新值存儲在... – Remko 2013-04-27 14:11:20

0

我用一個函數:

Function UndefinedFloat : double 
Begin 
    Result := Nan 
End; 

This then works 
Var 
    MyFloat : double; 

Begin 
    MyFloat := UndefinedFloat; 
+0

這是一個QNaN,但問題是關於SNaN – 2013-04-27 11:37:49

+0

對不起。我不明白區別。 – 2013-04-28 14:26:25

0

這是一個相當骯髒的方式做到這一點,在非常乾淨的代碼,結果消費者。

unit uSNaN; 

interface 

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

implementation 

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

end. 

我期待鏈接器將SNaN在只讀可執行的段,但它似乎沒有這樣做。無論如何,即使這樣做,您也可以在作業期間使用VirtualProtect來解決這個問題。