Was ist der Zweck des NaN-Boxens?

44

Als ich das 21. Jahrhundert C las , kam ich zu Kapitel 6 im Abschnitt "Markieren außergewöhnlicher numerischer Werte mit NaNs" , wo die Verwendung der Bits in der Mantisse zum Speichern von willkürlichen Bitmustern und deren Verwendung als Marker oder Zeiger erläutert wird (das Buch erwähnt dies) dass WebKit diese Technik verwendet).

Ich bin mir nicht sicher, ob ich die Nützlichkeit dieser Technik verstanden habe, die ich als Hack verstehe (sie beruht darauf, dass sich die Hardware nicht um den Wert der Mantisse in einem NaN kümmert), aber ich komme aus einem Java-Hintergrund, an den ich nicht gewöhnt bin die Rauheit von C.

Hier ist das Codefragment, mit dem ein Marker in einer NaN gesetzt und gelesen wird

#include <stdio.h>
#include <math.h> //isnan

double ref;

double set_na(){
    if (!ref) {
        ref=0/0.;
        char *cr = (char *)(&ref);
        cr[2]='a';
    }
    return ref;
}

int is_na(double in){
    if (!ref) return 0;  //set_na was never called==>no NAs yet.

    char *cc = (char *)(&in);
    char *cr = (char *)(&ref);
    for (int i=0; i< sizeof(double); i++)
        if (cc[i] != cr[i]) return 0;
    return 1;
}

int main(){
    double x = set_na();
    double y = x;
    printf("Is x=set_na() NA? %i\n", is_na(x));
    printf("Is x=set_na() NAN? %i\n", isnan(x));
    printf("Is y=x NA? %i\n", is_na(y));
    printf("Is 0/0 NA? %i\n", is_na(0/0.));
    printf("Is 8 NA? %i\n", is_na(8));
}

es druckt:

Is x=set_na() NA? 1
Is x=set_na() NAN? 1
Is y=x NA? 1
Is 0/0 NA? 0
Is 8 NA? 0

und bei JSValue.h erklärt das Webkit die Codierung, aber nicht, warum sie verwendet wird.

Was ist der Zweck dieser Technik? Sind die Vorteile von Speicherplatz / Leistung hoch genug, um seine hackige Natur auszugleichen?

andijcr
quelle
Können Sie ein einfaches Beispiel geben?
BЈовић
Um klar zu sein, fragt das OP, wo Signal-NaNs verwendet werden können
Ratschenfreak
1
@ratchetfreak, warum denkst du das?
Winston Ewert
@ratchetfreak: Es geht nicht darum, NaN zu signalisieren, wie das Webkit JSValue.h erklärt. Aber danke, dass ich etwas Neues entdecken durfte!
andijcr
1
@Hudson isnan () wird hauptsächlich im zweiten printf verwendet. Der Zweck von is_an () besteht darin, zu testen, ob das Bitmuster der Double-In-Eingabe dem in der globalen Variablen ref gespeicherten Bitmuster entspricht.
andijcr

Antworten:

63

Wenn Sie eine dynamisch typisierte Sprache implementieren, müssen Sie einen einzigen Typ haben, der alle Ihre Objekte aufnehmen kann. Dafür gibt es drei verschiedene Ansätze, die mir bekannt sind:

Erstens können Sie Zeiger weitergeben. Das macht die CPython-Implementierung. Jedes Objekt ist ein PyObjectZeiger. Diese Zeiger werden weitergegeben, und Operationen werden ausgeführt, indem Details in der PyObject-Struktur betrachtet werden, um den Typ zu ermitteln.

Der Nachteil ist, dass kleine Werte wie Zahlen als Box-Werte gespeichert werden. So wird Ihre kleine 5 irgendwo als Speicherblock gespeichert. Das führt uns also zu dem von Lua verwendeten Unionsansatz. Anstelle von a PyObject*ist jeder Wert eine Struktur, in der ein Feld den Typ angibt, und dann eine Vereinigung aller verschiedenen unterstützten Typen. Auf diese Weise vermeiden wir, kleinen Werten Speicher zuzuweisen, statt sie direkt in der Union zu speichern.

Der NaNAnsatz speichert alles als Doppelte und verwendet den nicht verwendeten Teil von NaNfür den zusätzlichen Speicher. Der Vorteil gegenüber der Vereinigungsmethode ist, dass wir das Typfeld speichern. Wenn es ein gültiges Double ist, ist es ein Double, andernfalls ist die Mantisse ein Zeiger auf das tatsächliche Objekt.

Denken Sie daran, dies ist jedes Javascript-Objekt. Jede Variable, jeder Wert in einem Objekt, jeder Ausdruck. Wenn wir all das von 96 auf 64 Bit reduzieren können, ist das ziemlich beeindruckend.

Lohnt sich der Hack? Denken Sie daran, dass ein effizientes Javascript sehr gefragt ist. Javascript ist der Engpass in vielen Webanwendungen, weshalb es eine höhere Priorität hat, es schneller zu machen. Aus Performancegründen ist es sinnvoll, ein gewisses Maß an Hackiness einzuführen. In den meisten Fällen wäre es eine schlechte Idee, weil sie einen gewissen Grad an Komplexität mit wenig Gewinn einführt. In diesem speziellen Fall lohnt es sich jedoch, den Speicher und die Geschwindigkeit zu verbessern.

Winston Ewert
quelle
2
Tatsächlich speichert CPython kleine Zahlen. Siehe hg.python.org/cpython/file/e6cc582cafce/Objects/longobject.c
Phillip Cloud
1
@cpcloud, stimmt, aber dieses Detail schien nicht relevant zu sein.
Winston Ewert
1
@ WinstonEwert Du hast recht. Ich dachte dasselbe, nachdem ich gelesen hatte, was ich geschrieben hatte.
Phillip Cloud
2
Die Verwendung von Bits eines primitiven Typs, um das "Boxen" aller Werte zu vermeiden, ist eine altehrwürdige Technik. Smalltalk verwendete es in den 1970er Jahren und stahl ein Bit aus 16-Bit-Ganzzahlen, um entweder einen Objektzeiger oder 15-Bit-Werte zu signalisieren SmallInteger.
Jonathan Eunice
2
@ JonathanEunice, wirklich? Das überrascht mich nur, weil es in 16 Bits wirklich nicht viel Reichweite gibt, auf die ich gerne verzichten würde.
Winston Ewert
7

Die Verwendung von NaN für "außergewöhnliche Werte" ist eine bekannte und manchmal hilfreiche Technik, um die Notwendigkeit einer zusätzlichen booleschen Variablen zu vermeiden this_value_is_invalid. Mit Bedacht verwendet, kann es einem helfen, seinen Code übersichtlicher, sauberer, einfacher und besser lesbar zu machen, ohne Kompromisse bei der Leistung eingehen zu müssen.

Diese Technik hat natürlich einige Tücken (siehe hier http://ppkwok.blogspot.co.uk/2012/11/java-cafe-1-never-write-nan-nan_24.html ), aber in Sprachen wie Java ( oder sehr ähnlich C #) gibt es Standard-Bibliotheksfunktionen Float.isNaN, die den Umgang mit NaNs vereinfachen. Natürlich könnten Sie in Java alternativ die Floatund DoubleKlasse und in C # die nullbaren Werttypen float?und verwenden double?, wodurch Sie die Möglichkeit haben, nullanstelle von NaN ungültige Gleitkommazahlen zu verwenden, aber diese Techniken können die Leistung und den Speicher erheblich negativ beeinflussen Nutzung Ihres Programms.

In C ist die Verwendung von NaN zwar nicht zu 100% portabel, aber Sie können es überall dort verwenden, wo der Gleitkommastandard IEEE 754 verfügbar ist. AFAIK dies ist heute fast jede Mainstream-Hardware (oder zumindest die Laufzeitumgebung der meisten Compiler unterstützt dies). Beispielsweise enthält dieser SO-Beitrag einige Informationen, um weitere Einzelheiten zur Verwendung von NaN in C zu erfahren.

Doc Brown
quelle
das auto-boxen in java ist chaotisch und sollte vermieden werden, es ist lächerlich und anfällig für fehler
ratschenfreak
Ich habe die Frage bearbeitet, um zu verlinken, wo das Webkit NaN-Boxen verwendet. Es scheint, dass das Webkit eine breitere Verwendung von NaN hat, als 'NaN' zu signalisieren
zwar am
2
@ Ratchetfreak: das unterstützt meinen Standpunkt natürlich
Doc Brown