2013-04-27 15 views
10

Lo standard IEEE754 definisce due classi di NaN, il NaN silenzioso, QNaN e la segnalazione NaN, SNaN. Quando un SNaN viene caricato in un registro a virgola mobile, viene sollevata un'eccezione dall'unità in virgola mobile.Come posso rendere semplice la segnalazione dei NaN?

QNaN è disponibile per il codice Delphi tramite la costante denominatadichiarata in Math. La definizione di tale costante è:

const 
    NaN = 0.0/0.0; 

Mi piacerebbe essere in grado di usare qualcosa di simile a dichiarare una costante che è una segnalazione NaN, ma non ho ancora trovato un modo per farlo.

Ingenuamente si potrebbe scrivere questo codice:

function SNaN: Double; 
begin 
    PInt64(@Result)^ := $7FF7FFFFFFFFFFFF;//this bit pattern specifies an SNaN 
end; 

Ma l'ABI per il galleggiamento valori di ritorno punto significa che lo Snan viene caricato in un registro in virgola mobile in modo che possa essere restituito. Naturalmente ciò porta a un'eccezione che sconfigge piuttosto lo scopo.

così si è poi portato alla scrittura di codice come questo:

procedure SetToSNaN(out D: Double); 
begin 
    PInt64(@D)^ := $7FF7FFFFFFFFFFFF; 
end; 

Ora, questo funziona, ma è molto scomodo. Supponiamo di dover passare un SNaN ad un'altra funzione. Idealmente si desidera scrivere:

Foo(SNaN) 

ma invece si deve fare questo:

var 
    SNaN: Double; 
.... 
SetToSNaN(SNaN); 
Foo(SNaN); 

Così, dopo il build-up, ecco la domanda.

C'è un modo per scrivere x := SNaN e la variabile in virgola mobile x ha assegnato un valore che è un NaN di segnalazione?

+0

Hai provato a inserire il tuo primo approccio? –

+1

@UweRaabe In effetti ho. Volevo che qualcun altro scrivesse quella risposta in modo che potessero ottenere il rappresentante. Mi sento ancora un po 'a disagio nel fare affidamento su inlining. Se la chiamata di funzione non è in qualche modo in linea, quindi boom. –

risposta

8

Questa dichiarazione risolve in fase di compilazione:

const 
    iNaN : UInt64 = $7FF7FFFFFFFFFFFF; 
var 
    SNaN : Double absolute iNaN; 

Il compilatore per trattamento ancora SNaN come costante.

Tentare di assegnare un valore a SNaN darà un errore di tempo di compilazione: 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" 

caso si ha la direttiva del compilatore $WRITEABLECONSTS ON (o $J+), questo potrebbe essere spento temporaneamente per garantire non alterare SNaN.

{$IFOPT J+} 
    {$DEFINE UNDEFWRITEABLECONSTANTS} 
    {$J-} 
{$ENDIF} 

const 
    iNaN : UInt64 = $7FF7FFFFFFFFFFFF; 
var 
    SNaN : Double ABSOLUTE iNaN; 

{$IFDEF UNDEFWRITEABLECONSTANTS} 
    {$J+} 
{$ENDIF} 
+0

+1 Questo fa il trucco, ma sentirò un po 'di nausea sul valore che vive in una variabile piuttosto che in una costante. –

+0

Assegnare un valore a SNaN darà un errore del compilatore: 'E2064 La parte sinistra non può essere assegnata a'. –

+0

Ah, capisco. Davvero ben fatto. –

4

È possibile inline la funzione:

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

Ma dipenderà l'ottimizzazione e il compilatore d'animo.

Ho visto alcune funzioni non in linea, senza una chiara comprensione dal contesto. Non mi piace né fare affidamento su inlining.

Quello che vorrei meglio fare, e che funziona su tutte le versioni di Delphi, è quello di utilizzare una variabile globale:

var 
    SNaN: double; 

Quindi impostare nella initialization blocco dell'unità:

const 
    SNaN64 = $7FF7FFFFFFFFFFFF; 

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

Quindi sarai in grado di utilizzare SNaN come costante regolare. Cioè, è possibile scrivere codice come previsto:

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

Nel debugger IDE, sarà mostrato come "test = + NAN", che è il risultato atteso, suppongo.

Si noti che l'utilizzo di questo SNaN genera un'eccezione quando viene letto nello stack FPU (ad es.if test=0 then), quindi è necessario controllare il valore a livello binario ... questo è il motivo per cui ho definito una costante SNaN64, che renderà il codice molto veloce a proposito.

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); 

È possibile modificare questo comportamento modificando il registro eccezione x87:

backup := Set8087CW($133F); 
try 
    .. 
finally 
    Set8087CW(backup); 
end; 

Suppongo che questo da impostare a livello globale per il programma, in tutta estendere il codice che dovrà gestire questo SNaN costante.

+0

Non sta sollevando l'eccezione l'intero scopo di una segnalazione NaN? –

+0

Secondo la mia esperienza, se la funzione è definita al ** start ** della sezione di implementazione dell'unità che è ** compilata per prima **, la funzione sarà sempre in linea. –

+1

Per quanto riguarda i test per SNaN, esistono più pattern di bit che sono SNaN. Quindi hai bisogno di un test migliore. La funzione IsNaN in Math è un buon punto di partenza. –

4

Ecco un'altra soluzione:

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

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

Il debugger mostra IEEE754.SNaN come + NAN, tuttavia quando si accede ci si può comunque un'eccezione a virgola mobile. Una soluzione per questo potrebbe essere:

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

Sembra che il modello RAII stia decollando nella terra di Delfi. ;-) –

+0

+1 Mi piace molto il record della variante. A proposito, lo sapevate che 'Set8087CW' non è protetto da thread? –

+0

@DavidHeffernan yes, Delphi memorizza il nuovo valore nella variabile Default8087CW ... – Remko

0

io uso una funzione:

Function UndefinedFloat : double 
Begin 
    Result := Nan 
End; 

This then works 
Var 
    MyFloat : double; 

Begin 
    MyFloat := UndefinedFloat; 
+0

Questo è un QNaN ma la domanda riguarda SNaN –

+0

Ah scusa. Non ho capito la differenza –

0

Ecco un modo piuttosto sporca per farlo, che si traduce in codice molto pulito per il consumatore.

unit uSNaN; 

interface 

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

implementation 

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

end. 

mi aspettavo il linker di mettere SNaN in una sola lettura segmento del file eseguibile ma sembra di non farlo. In ogni caso, anche se fosse così, è possibile utilizzare VirtualProtect per aggirare questo per la durata del compito.

Problemi correlati