Frage

Ich habe vor kurzem ziemlich viel auf IEEE 754 und der x87-Architektur nachlesen. Ich dachte an NaN als „fehlenden Wert“ in einem gewissen numerischen Berechnung Code auf ich arbeite, und ich habe gehofft, dass mit Signalisierung NaN würde mir erlauben, eine Gleitkomma-Ausnahme in den Fällen zu fangen wo ich will nicht mit gehen „fehlenden Werten.“ Im Gegensatz dazu würde ich Ruhe NaN das ermöglichen „fehlende Wert“ durch eine Berechnung zu propagieren. Allerdings signalisiert NaNs nicht arbeiten, wie ich dachte, dass sie auf die (sehr begrenzt) Dokumentation basieren würde, die auf ihnen vorhanden ist.

Hier ist eine Zusammenfassung dessen, was ich weiß (alle diese unter Verwendung von x87 und VC ++):

  • _EM_INVALID (die IEEE "ungültig" Ausnahme) steuert das Verhalten des x87 wenn NaNs Begegnung
  • Wenn _EM_INVALID maskiert ist (die Ausnahme ist deaktiviert), keine Ausnahme erzeugt und Operationen können ruhig NaN zurück. Eine Operation mit NaN Signalisierung wird nicht Ursache eine Ausnahme ausgelöst werden, aber zu beruhigen NaN umgewandelt wird.
  • Wenn _EM_INVALID unmaskiert ist (Ausnahme aktiviert ist), eine ungültige Operation (zum Beispiel sqrt (-1)) bewirkt, dass eine ungültige Ausnahme ausgelöst werden.
  • Die x87 nie erzeugt Signalisierung NaN.
  • Wenn _EM_INVALID unmaskiert ist, jede Verwendung eines Signalisierungs NaN (auch eine Variable mit ihm zu initialisieren) bewirkt, dass eine ungültigen Ausnahme ausgelöst werden.

Der Standard Library bietet eine Möglichkeit, die NaN-Werte zuzugreifen:

std::numeric_limits<double>::signaling_NaN();

und

std::numeric_limits<double>::quiet_NaN();

Das Problem ist, dass ich keinen Gebrauch auch immer für den Signal-NAN sehen. Wenn _EM_INVALID maskiert ist es verhält sich genau das gleiche wie ruhig NaN. Da kein NaN zu jedem anderen NaN vergleichbar ist, gibt es keinen logischen Unterschied.

Wenn _EM_INVALID ist nicht maskiert (Ausnahme aktiviert ist), dann kann man nicht einmal eine Variable mit einem Signalisierungs NaN initialisieren: double dVal = std::numeric_limits<double>::signaling_NaN(); weil diese eine Ausnahme auslöst (die Signalisierungs NaN Wert wird in ein x87 geladen Register zu speichern, um die Speicheradresse).

Sie denken vielleicht, das folgende wie ich:

  1. Maske _EM_INVALID.
  2. initialisieren die Variable mit Signal NaN.
  3. Unmask_EM_INVALID.

Allerdings Schritt 2 bewirkt, daß das Signal-NAN zu einem ruhigen umgewandelt werden NaN, so nachfolgenden Verwendungen davon werden nicht Ursache Ausnahmen geworfen werden! So WTF?!

Gibt es einen Nutzen oder Zweck auch immer zu einem Signalisieren NaN? Ich verstehe, einer der ursprünglichen Absichten war mit ihm Speicher zu initialisieren, so dass die Verwendung eines unitialized könnte Gleitkommawert gefangen werden.

Kann mir jemand sagen, ob ich etwas fehlt hier?


EDIT:

Zur weiteren Veranschaulichung, was ich gehofft hatte zu tun, hier ist ein Beispiel:

Betrachten Sie mathematische Operationen auf einem Vektor von Daten (Doppel) durchgeführt wird. Für einige Operationen, möchte ich den Vektor ermöglichen, einen „fehlenden Wert“ zu enthalten (so tun, dies entspricht einer Tabellenspalte, zum Beispiel, in denen ein Teil der Zellen keinen Wert haben, aber ihre Existenz ist signifikant). Für einige Operationen, ich nicht wollen, dass der Vektor zu ermöglichen, einen enthalten „fehlenden Wert.“ Vielleicht möchte ich eine andere Vorgehensweise nehmen, wenn ein „fehlender Wert“ in der Reihe ist - vielleicht eine andere Operation durchführt (so ist dies nicht ein ungültiger Zustand zu sein in)

.

Dieser ursprüngliche Code würde wie folgt aussehen:

const double MISSING_VALUE = 1.3579246e123;
using std::vector;

vector<double> missingAllowed(1000000, MISSING_VALUE);
vector<double> missingNotAllowed(1000000, MISSING_VALUE);

// ... populate missingAllowed and missingNotAllowed with (user) data...

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
    if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation
}

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
    if (*it != MISSING_VALUE) *it = sqrt(*it);
    else *it = 0;
}

Beachten Sie, dass die Prüfung für den „fehlenden Wert“ durchgeführt werden muß jede Schleifeniterationslatenzzeit . Während ich in den meisten Fällen die sqrt Funktion (oder eine andere mathematische Operation) wird wahrscheinlich diese Prüfung überschattet, gibt es Fälle, zu verstehen, wo die Operation minimal ist (vielleicht nur eine Ergänzung) und die Prüfung ist teuer. Nicht zu erwähnen, dass der „fehlende Wert“ nimmt einen Rechtseingabewert aus dem Spiel und konnte Fehler verursachen, wenn eine Berechnung legitimkommt bei diesem Wert (unwahrscheinlich sie auch sein mag). Auch technisch korrekt zu sein, sollten die Benutzer Eingabedaten gegen diesen Wert überprüft werden und eine geeignete Vorgehensweise getroffen werden sollte. Ich diese Lösung unelegant finden und weniger als optimalen Performance-weise. Dies ist leistungskritischen Code, und wir haben auf jeden Fall nicht den Luxus von parallelen Datenstrukturen oder Datenelementobjekten von einer Art.

Die NaN Version würde wie folgt aussehen:

using std::vector;

vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN());
vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN());

// ... populate missingAllowed and missingNotAllowed with (user) data...

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
    *it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN
}

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
    try {
        *it = sqrt(*it);
    } catch (FPInvalidException&) { // assuming _seh_translator set up
        *it = 0;
    }
}

Nun ist die explizite Kontrolle eliminiert wird und die Leistung verbessert werden soll. Ich denke, das würde alle Arbeit, wenn ich ohne sie zu berühren die FPU Register den Vektor initialisieren könnte ...

Darüber hinaus könnte ich mir vorstellen keine Selbstachtung sqrt Implementierung prüft NaN und kehrt NaN sofort.

War es hilfreich?

Lösung

Wie ich es verstehe, ist der Zweck NaN zu signalisieren, ist Datenstrukturen zu initialisieren, aber natürlich Laufzeit Initialisierung in C läuft das Risiko, dass die NaN geladen in ein Schwimmerregister als Teil der Initialisierung und löst damit das Signal, weil der der Compiler, dass diese float-Wert muss ein integer-Register kopiert werden nicht bewusst ist, verwendet wird.

Ich würde hoffen, dass Sie einen static Wert mit einem Signalisierungs NaN initialisieren konnten konnten, aber selbst das würde einige spezielle Handhabung durch den Compiler erfordern zu vermeiden, dass es zu einer ruhigen NaN umgewandelt. Man könnte vielleicht ein bisschen Gießen Magie zu vermeiden, sie als Float-Wert bei der Initialisierung behandelt wird.

Wenn Sie in ASM geschrieben hat, wäre dies kein Problem sein. aber in C und besonders in C ++, ich glaube, das Typ-System, um musst unterminieren eine Variable mit NaN zu initialisieren. Ich schlage vor, mit memcpy.

Andere Tipps

Mit speziellen Werten (auch NULL) kann Ihre Daten viel trüber und Ihren Code viel chaotischer machen. Es wäre unmöglich, zwischen einem QNaN Ergebnis und einem QNaN „special“ Wert zu unterscheiden.

Sie könnten besser sein, eine parallele Datenstruktur beibehalten Gültigkeit zu verfolgen, oder vielleicht in einer anderen (spärlichen) Datenstruktur Ihre FP-Daten mit nur gültigen Daten zu halten.

Dies ist eine ziemlich allgemeine Beratung; Sonderwerte sind sehr nützlich, in bestimmten Fällen (zum Beispiel wirklich engen Speicher oder Leistungseinschränkungen), aber wie der Kontext größer wird sie mehr Schwierigkeiten verursachen, als sie wert ist.

Könnten Sie nicht einfach nur ein const uint64_t wo die Bits, die die einen Signalisierungs nan Satz gewesen sein? solange Sie es als eine Integer-Typ zu behandeln, ist die Signalisierung nan nicht unterscheidet sich von anderen Zahlen. Man könnte es schreiben, wo Sie durch Zeiger-Casting wollen:

Const uint64_t sNan = 0xfff0000000000000;
Double[] myData;
...
Uint64* copier = (uint64_t*) &myData[index];
*copier=sNan | myErrorFlags;

Für Informationen über die Bits zu setzen: https://www.doc.ic.ac.uk/ ~ eedwards / compsys / float / nan.html

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top