2013-04-27 4 views
10

IEEE754標準では、2つのクラスの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にシグナリングNaNの値が割り当てられる方法はありますか?

+0

最初のアプローチをインライン展開しようとしましたか? –

+1

@UweRaabe実際に私は持っています。私は誰か他の人がその回答を書くようにして、担当者を得ることができるようにしたかったのです。私はインライン展開に頼っても少し不安です。関数呼び出しが何らかの形でインライン化されていない場合は、ブームします。 –

答えて

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それはトリックですが、定数ではなく変数に生きる価値について気が狂っています。 –

+0

SNaNに値を割り当てると、 'E2064左側に割り当てることができません 'というコンパイラエラーが発生します。 –

+0

ああ、そうだ。確かに非常にうまくいった。 –

4

あなたは機能インライン展開することができます:

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

をしかし、それは、最適化コンパイラ気分に依存します。

文脈からはっきり理解できないうちに、インライン化されていない関数がいくつか見られました。私はインライン展開に頼るのが好きではありません。その後

var 
    SNaN: double; 

単位のinitializationブロックでそれを設定します:

const 
    SNaN64 = $7FF7FFFFFFFFFFFF; 

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

は私がより良いどうなるのか、とDelphiのすべてのバージョンで動作しますこれは、グローバル変数を使用することです

次に、SNaNを通常の定数として使用することができます。これは予想通り、あなたがコードを書くことができます:IDEデバッガで

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

を、それが予想される結果である「テスト= + 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の目的は例外ではありませんか? –

+0

私の経験上、関数が**最初に**コンパイルされたユニットの実装セクションの** start **に定義されている場合、その関数は常にインライン展開されます。 –

+1

SNaNのテストに関しては、SNaNである複数のビットパターンがあります。だから、より良いテストが必要です。 MathのIsNaN関数は良い出発点です。 –

4

は別の回避策です:デバッガは+ NANとしてIEEE754.SNaNを示し

type 
    TFakeRecord = record 
    case Byte of 
     0: (SNaN: Double); 
     1: (i: Int64); 
    end; 

const 
    IEEE754: TFakeRecord = (i: $7FF7FFFFFFFFFFFF); 

あなたがそれにアクセスすると、しかし、あなたはまだ浮動小数点例外を取得します。それを回避するには、次のようになります。

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パターンがDelphiの土地で離陸しているように見えます。 ;-) –

+0

+1私は変種レコードが大好きです。ところで、あなたは 'Set8087CW'がスレッドセーフではないことを知っていますか? –

+0

@DavidHeffernanはい、DelphiはDefault8087CW変数に新しい値を保存します... – Remko

0

私は、関数を使用します。

Function UndefinedFloat : double 
Begin 
    Result := Nan 
End; 

This then works 
Var 
    MyFloat : double; 

Begin 
    MyFloat := UndefinedFloat; 
+0

それはQNaNですが、質問はSNaNについてです –

+0

ああ、申し訳ありません。私はその違いを理解していませんでした。 –

0

ここでは消費者のための非常にクリーンなコードになりますこと、それを行うにはかなり汚い方法です。

unit uSNaN; 

interface 

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

implementation 

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

end. 

私は実行可能ファイルの読み取り専用のセグメントにSNaNを置くために、リンカを期待していたが、そうしないように表示されます。いずれにしても、割り当てられていたとしても、VirtualProtectを使用して回避することができます。

関連する問題