Was ist der Unterschied zwischen leisem NaN und signalisierendem NaN?
96
Ich habe über Gleitkomma gelesen und verstehe, dass NaN aus Operationen resultieren kann. Aber ich kann nicht genau verstehen, was das für Konzepte sind. Was ist der Unterschied zwischen ihnen?
Welches kann während der C ++ - Programmierung erzeugt werden? Könnte ich als Programmierer ein Programm schreiben, das eine sNaN verursacht?
Wenn eine Operation zu einem leisen NaN führt, gibt es keinen Hinweis darauf, dass etwas ungewöhnlich ist, bis das Programm das Ergebnis überprüft und ein NaN sieht. Das heißt, die Berechnung wird ohne Signal von der Gleitkommaeinheit (FPU) oder Bibliothek fortgesetzt, wenn Gleitkomma in Software implementiert ist. Ein signalisierendes NaN erzeugt ein Signal, normalerweise in Form einer Ausnahme von der FPU. Ob die Ausnahme ausgelöst wird, hängt vom Status der FPU ab.
Hinzu kommt: Im Allgemeinen dient ein Signalisierungs-NaN (sNaN) zum Debuggen. Beispielsweise können Gleitkommaobjekte mit sNaN initialisiert werden. Wenn das Programm dann vor der Verwendung einen Wert für einen von ihnen nicht erreicht, tritt eine Ausnahme auf, wenn das Programm den sNaN in einer arithmetischen Operation verwendet. Ein Programm erzeugt nicht versehentlich ein sNaN. Kein normaler Betrieb erzeugt sNaNs. Sie werden nur speziell für den Zweck eines NaN-Signals erstellt, nicht als Ergebnis einer Arithmetik.
Eric Postpischil
18
Im Gegensatz dazu dienen NaNs einer normaleren Programmierung. Sie können durch normale Operationen erzeugt werden, wenn kein numerisches Ergebnis vorliegt (z. B. die Quadratwurzel einer negativen Zahl ziehen, wenn das Ergebnis real sein muss). Ihr Zweck ist im Allgemeinen, die Arithmetik etwas normal ablaufen zu lassen. Beispielsweise haben Sie möglicherweise eine große Anzahl von Zahlen, von denen einige Sonderfälle darstellen, die nicht normal behandelt werden können. Sie können eine komplizierte Funktion aufrufen, um dieses Array zu verarbeiten, und es kann mit der üblichen Arithmetik auf dem Array arbeiten, wobei NaNs ignoriert werden. Nach dem Ende würden Sie die Sonderfälle für mehr Arbeit trennen.
Eric Postpischil
@wrdieter Danke, dann erzeugt nur ein großer Unterschied eine Ausnahme oder nicht.
JalalJaberi
@EricPostpischil Vielen Dank für Ihre Aufmerksamkeit für die zweite Frage.
JalalJaberi
@ JalalJaberi ja, die Ausnahme ist der Hauptunterschied
wrdieter
34
Wie sehen qNaNs und sNaNs experimentell aus?
Lassen Sie uns zunächst lernen, wie wir feststellen können, ob wir einen sNaN oder einen qNaN haben.
qNaN und sNaN scheinen nur durch Bit 22 unterschieden zu werden: 1 bedeutet leise und 0 bedeutet Signalisierung
Unendlichkeiten sind auch mit Exponent == 0xFF ziemlich ähnlich, aber sie haben Bruch == 0.
Aus diesem Grund müssen NaNs Bit 21 auf 1 setzen, sonst wäre es nicht möglich, sNaN von positiver Unendlichkeit zu unterscheiden!
nanf() erzeugt mehrere verschiedene NaNs, daher muss es mehrere mögliche Codierungen geben:
7fc00000
7fc00001
7fc00002
Da dies nan0dasselbe ist wie std::numeric_limits<float>::quiet_NaN(), schließen wir, dass es sich bei allen um unterschiedliche leise NaNs handelt.
Der Standardentwurf C11 N1570 bestätigt, dass nanf()leise NaNs erzeugt werden, da Weiterleitungen nanfan strtodund 7.22.1.3 "Die Funktionen strtod, strtof und strtold" lauten:
Eine Zeichenfolge NAN oder NAN (n-char-sequence opt) wird als leises NaN interpretiert, wenn sie im Rückgabetyp unterstützt wird, ansonsten wie ein Subjektsequenzteil, das nicht die erwartete Form hat. Die Bedeutung der n-Zeichen-Sequenz ist implementierungsdefiniert. 293)
IEEE 754 2008 empfiehlt Folgendes (TODO obligatorisch oder optional?):
Alles mit Exponent == 0xFF und Bruch! = 0 ist ein NaN
und dass das höchste Bruchbit qNaN von sNaN unterscheidet
aber es scheint nicht zu sagen, welches Bit bevorzugt wird, um die Unendlichkeit von NaN zu unterscheiden.
6.2.1 "NaN-Codierungen in binären Formaten" sagt:
Dieser Unterabschnitt spezifiziert ferner die Codierungen von NaNs als Bitfolgen, wenn sie das Ergebnis von Operationen sind. Bei der Codierung haben alle NaNs ein Vorzeichenbit und ein Muster von Bits, die erforderlich sind, um die Codierung als NaN zu identifizieren, und die ihre Art bestimmt (sNaN vs. qNaN). Die verbleibenden Bits, die sich im nachfolgenden Signifikantenfeld befinden, codieren die Nutzlast, bei der es sich möglicherweise um Diagnoseinformationen handelt (siehe oben). 34
Bei allen binären NaN-Bitfolgen sind alle Bits des vorgespannten Exponentenfelds E auf 1 gesetzt (siehe 3.4). Eine ruhige NaN-Bitfolge sollte codiert werden, wobei das erste Bit (d1) des nachlaufenden Signifikantenfelds T 1 ist. Eine signalisierende NaN-Bitfolge sollte codiert werden, wobei das erste Bit des nachfolgenden Signifikantenfelds 0 ist. Wenn das erste Bit der Das nachfolgende Signifikantenfeld ist 0, ein anderes Bit des nachfolgenden Signifikantenfelds muss ungleich Null sein, um das NaN von der Unendlichkeit zu unterscheiden. In der gerade beschriebenen bevorzugten Codierung soll ein Signalisierungs-NaN durch Setzen von d1 auf 1 beruhigt werden, wobei die verbleibenden Bits von T unverändert bleiben. Bei binären Formaten wird die Nutzlast in den niedrigstwertigen p-2-Bits des nachfolgenden Signifikantenfelds codiert
Die IA-32-Architektur definiert zwei Klassen von NaNs: leise NaNs (QNaNs) und signalisierende NaNs (SNaNs). Ein QNaN ist ein NaN, bei dem das höchstwertige Bruchteil gesetzt ist. Ein SNaN ist ein NaN, bei dem das höchstwertige Bruchteil frei ist.
fraction != 0: Der Wert ist ein NaN und entweder ein leises NaN oder ein signalisierendes NaN. Die beiden Arten von NaN unterscheiden sich durch ihr höchstwertiges Bruchbit, Bit [22]:
bit[22] == 0: Das NaN ist ein signalisierendes NaN. Das Vorzeichenbit kann einen beliebigen Wert annehmen, und die verbleibenden Bruchbits können einen beliebigen Wert außer allen Nullen annehmen.
bit[22] == 1: Das NaN ist ein leises NaN. Das Vorzeichenbit und die verbleibenden Bruchbits können einen beliebigen Wert annehmen.
Wie werden qNanS und sNaNs generiert?
Ein Hauptunterschied zwischen qNaNs und sNaNs besteht darin, dass:
qNaN wird durch reguläre integrierte (Software oder Hardware) arithmetische Operationen mit seltsamen Werten generiert
sNaN wird niemals durch eingebaute Operationen erzeugt, sondern kann nur von Programmierern explizit hinzugefügt werden, z. B. mit std::numeric_limits::signaling_NaN
Ich konnte keine eindeutigen IEEE 754- oder C11-Anführungszeichen dafür finden, aber ich kann auch keine eingebaute Operation finden, die sNaNs erzeugt ;-)
Das Intel-Handbuch gibt dieses Prinzip jedoch unter 4.8.3.4 "NaNs" deutlich an:
SNaNs werden normalerweise verwendet, um einen Ausnahmebehandler abzufangen oder aufzurufen. Sie müssen per Software eingefügt werden. Das heißt, der Prozessor generiert niemals eine SNaN als Ergebnis einer Gleitkommaoperation.
Dies ist aus unserem Beispiel ersichtlich, in dem beide:
produzieren genau die gleichen Bits wie std::numeric_limits<float>::quiet_NaN().
Beide Vorgänge werden zu einer einzigen x86-Assemblyanweisung kompiliert, die den qNaN direkt in der Hardware generiert (TODO-Bestätigung mit GDB).
Was machen qNaNs und sNaNs unterschiedlich?
Jetzt, da wir wissen, wie qNaNs und sNaNs aussehen und wie man sie manipuliert, sind wir endlich bereit, sNaNs dazu zu bringen, ihr Ding zu machen und einige Programme in die Luft zu jagen!
Also ohne weiteres:
blow_up.cpp
#include <cassert>
#include <cfenv>
#include <cmath> // isnan
#include <iostream>
#include <limits> // std::numeric_limits
#include <unistd.h>
#pragma STDC FENV_ACCESS ON
int main() {
float snan = std::numeric_limits<float>::signaling_NaN();
float qnan = std::numeric_limits<float>::quiet_NaN();
float f;
// No exceptions.
assert(std::fetestexcept(FE_ALL_EXCEPT) == 0);
// Still no exceptions because qNaN.
f = qnan + 1.0f;
assert(std::isnan(f));
if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID)
std::cout << "FE_ALL_EXCEPT qnan + 1.0f" << std::endl;
// Now we can get an exception because sNaN, but signals are disabled.
f = snan + 1.0f;
assert(std::isnan(f));
if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID)
std::cout << "FE_ALL_EXCEPT snan + 1.0f" << std::endl;
feclearexcept(FE_ALL_EXCEPT);
// And now we enable signals and blow up with SIGFPE! >:-)
feenableexcept(FE_INVALID);
f = qnan + 1.0f;
std::cout << "feenableexcept qnan + 1.0f" << std::endl;
f = snan + 1.0f;
std::cout << "feenableexcept snan + 1.0f" << std::endl;
}
Kompilieren, ausführen und den Exit-Status erhalten:
Beachten Sie, dass dieses Verhalten nur -O0in GCC 8.2 auftritt: mit-O3 auftritt berechnet GCC alle unsere sNaN-Operationen vorab und optimiert sie! Ich bin mir nicht sicher, ob es eine standardkonforme Möglichkeit gibt, dies zu verhindern.
Aus diesem Beispiel schließen wir also:
snan + 1.0Ursachen FE_INVALID, aber qnan + 1.0nicht
Linux erzeugt nur dann ein Signal, wenn es mit aktiviert ist feenableexept.
Dies ist eine glibc-Erweiterung, ich konnte in keinem Standard einen Weg finden, dies zu tun.
Wenn das Signal auftritt, wird die CPU-Hardware selbst eine Ausnahme auslösen, die der Linux-Kernel behandelt und die Anwendung über das Signal informiert hat.
Das Ergebnis ist , dass bash druckt Floating point exception (core dumped), und der Ausgangszustand ist 136, der dem entspricht , Signal 136 - 128 == 8, das gemäß:
man 7 signal
ist SIGFPE.
Beachten Sie, dass dies SIGFPEdasselbe Signal ist, das wir erhalten, wenn wir versuchen, eine Ganzzahl durch 0 zu teilen:
int main() {
int i = 1 / 0;
}
obwohl für ganze Zahlen:
Wenn Sie etwas durch Null teilen, wird das Signal erhöht, da es keine Ganzzahldarstellung in ganzen Zahlen gibt
das Signal, das standardmäßig auftritt, ohne dass dies erforderlich ist feenableexcept
Wie gehe ich mit dem SIGFPE um?
Wenn Sie nur einen Handler erstellen, der normal zurückgibt, führt dies zu einer Endlosschleife, da die Division nach der Rückkehr des Handlers erneut erfolgt! Dies kann mit GDB überprüft werden.
Ganz ehrlich, ich habe immer noch keinen besonders nützlichen Anwendungsfall für sNaNs verstanden. Dies wurde gefragt unter: Nützlichkeit der Signalisierung von NaN?
sNaNs fühlen sich besonders nutzlos an, weil wir die anfänglich ungültigen Operationen ( 0.0f/0.0f) erkennen können, die qNaNs erzeugen mit feenableexcept: Es scheint, dass snannur Fehler für weitere Operationen ausgelöst werden, die nicht ausgelöst werden qnan, z.qnan + 1.0f . ).
#include <fenv.h>
int
feenableexcept (int excepts)
{
unsigned short int new_exc, old_exc;
unsigned int new;
excepts &= FE_ALL_EXCEPT;
/* Get the current control word of the x87 FPU. */
__asm__ ("fstcw %0" : "=m" (*&new_exc));
old_exc = (~new_exc) & FE_ALL_EXCEPT;
new_exc &= ~excepts;
__asm__ ("fldcw %0" : : "m" (*&new_exc));
/* And now the same for the SSE MXCSR register. */
__asm__ ("stmxcsr %0" : "=m" (*&new));
/* The SSE exception masks are shifted by 7 bits. */
new &= ~(excepts << 7);
__asm__ ("ldmxcsr %0" : : "m" (*&new));
return old_exc;
}
Was sagt der C-Standard über qNaN vs sNaN?
Der Standardentwurf C11 N1570 besagt ausdrücklich, dass der Standard bei F.2.1 "Unendlichkeiten, vorzeichenbehaftete Nullen und NaNs" nicht zwischen ihnen unterscheidet:
1 Diese Spezifikation definiert nicht das Verhalten der Signalisierung von NaNs. Es wird allgemein der Begriff NaN verwendet, um ruhige NaNs zu bezeichnen. Die NAN- und INFINITY-Makros sowie die Nan-Funktionen enthalten <math.h>Bezeichnungen für NaNs und Infinities nach IEC 60559.
Getestet in Ubuntu 18.10, GCC 8.2. GitHub Upstreams:
en.wikipedia.org/wiki/IEEE_754#Interchange_formats weist darauf hin, dass IEEE-754 lediglich vorschlägt, dass 0 für die Signalisierung von NaNs eine gute Wahl für die Implementierung ist, um die Beruhigung eines NaN zu ermöglichen, ohne das Risiko einzugehen, dass es unendlich wird (Signifikand = 0). Anscheinend ist das nicht standardisiert, obwohl es das ist, was x86 tut. (Und die Tatsache, dass es das MSB des Signifikanten ist, das qNaN vs. sNaN bestimmt, ist standardisiert). en.wikipedia.org/wiki/Single-precision_floating-point_format sagt, dass x86 und ARM gleich sind, aber PA-RISC hat die entgegengesetzte Wahl getroffen.
Peter Cordes
@PeterCordes Ja, ich bin nicht sicher, was das "sollte" == "muss" oder "bevorzugt" in IEEE 754 20at "Eine signalisierende NaN-Bitfolge sollte codiert werden, wobei das erste Bit des nachfolgenden Signifikantenfelds 0 ist".
Ciro Santilli 法轮功 冠状 病. 事件 12
re: aber es scheint nicht anzugeben, welches Bit verwendet werden soll, um Unendlichkeit von NaN zu unterscheiden. Sie haben geschrieben, dass es, wie Sie erwartet haben, ein bestimmtes Bit gibt, das der Standard empfiehlt, um sNaN von unendlich zu unterscheiden. IDK, warum Sie erwarten würden, dass es so etwas gibt; Jede Wahl ungleich Null ist in Ordnung. Wählen Sie einfach etwas aus, das später identifiziert, woher der sNaN stammt. IDK, klingt nur nach seltsamer Phrasierung, und mein erster Eindruck beim Lesen war, dass Sie sagten, dass die Webseite nicht beschreibt, was inf von NaN in der Codierung unterscheidet (ein All-Null-Signifikant).
Peter Cordes
Vor 2008 sagte IEEE 754, welches das Signalisierungs- / Ruhebit (Bit 22) ist, aber nicht welcher Wert was spezifizierte. Die meisten Prozessoren waren auf 1 = leise konvergiert, so dass dies in der Ausgabe 2008 zum Standard gemacht wurde. Es heißt "sollte" statt "muss", um zu vermeiden, dass ältere Implementierungen, die dieselbe Auswahl getroffen haben, nicht konform sind. Im Allgemeinen bedeutet "sollte" in einem Standard "muss", es sei denn, Sie haben sehr zwingende (und vorzugsweise gut dokumentierte) Gründe für die Nichteinhaltung ".
Wie sehen qNaNs und sNaNs experimentell aus?
Lassen Sie uns zunächst lernen, wie wir feststellen können, ob wir einen sNaN oder einen qNaN haben.
Ich werde in dieser Antwort C ++ anstelle von C verwenden, da es das Praktische bietet
std::numeric_limits::quiet_NaN
undstd::numeric_limits::signaling_NaN
das ich in C nicht bequem finden konnte.Ich konnte jedoch keine Funktion zum Klassifizieren finden, wenn ein NaN sNaN oder qNaN ist. Drucken wir also einfach die NaN-Rohbytes aus:
main.cpp
Kompilieren und ausführen:
Ausgabe auf meinem x86_64-Computer:
Wir können das Programm auch auf aarch64 im QEMU-Benutzermodus ausführen:
und das erzeugt genau die gleiche Ausgabe, was darauf hindeutet, dass mehrere Bögen IEEE 754 eng implementieren.
Wenn Sie an dieser Stelle nicht mit der Struktur von IEEE 754-Gleitkommazahlen vertraut sind, schauen Sie sich Folgendes an: Was ist eine subnormale Gleitkommazahl?
In binärer Form sind einige der obigen Werte:
Aus diesem Experiment beobachten wir, dass:
qNaN und sNaN scheinen nur durch Bit 22 unterschieden zu werden: 1 bedeutet leise und 0 bedeutet Signalisierung
Unendlichkeiten sind auch mit Exponent == 0xFF ziemlich ähnlich, aber sie haben Bruch == 0.
Aus diesem Grund müssen NaNs Bit 21 auf 1 setzen, sonst wäre es nicht möglich, sNaN von positiver Unendlichkeit zu unterscheiden!
nanf()
erzeugt mehrere verschiedene NaNs, daher muss es mehrere mögliche Codierungen geben:Da dies
nan0
dasselbe ist wiestd::numeric_limits<float>::quiet_NaN()
, schließen wir, dass es sich bei allen um unterschiedliche leise NaNs handelt.Der Standardentwurf C11 N1570 bestätigt, dass
nanf()
leise NaNs erzeugt werden, da Weiterleitungennanf
anstrtod
und 7.22.1.3 "Die Funktionen strtod, strtof und strtold" lauten:Siehe auch:
Wie sehen qNaNs und sNaNs in den Handbüchern aus?
IEEE 754 2008 empfiehlt Folgendes (TODO obligatorisch oder optional?):
aber es scheint nicht zu sagen, welches Bit bevorzugt wird, um die Unendlichkeit von NaN zu unterscheiden.
6.2.1 "NaN-Codierungen in binären Formaten" sagt:
Das Softwareentwicklerhandbuch für Intel 64- und IA-32-Architekturen - Band 1 - Grundlegende Architektur - 253665-056US September 2015 4.8.3.4 "NaNs" bestätigen, dass x86 IEEE 754 folgt, indem NaN und sNaN durch das Bit mit dem höchsten Bruch unterschieden werden:
und das ARM Architecture-Referenzhandbuch - ARMv8 für das ARMv8-A-Architekturprofil - DDI 0487C.a A1.4.3 "Gleitkommaformat mit einfacher Genauigkeit":
fraction != 0
: Der Wert ist ein NaN und entweder ein leises NaN oder ein signalisierendes NaN. Die beiden Arten von NaN unterscheiden sich durch ihr höchstwertiges Bruchbit, Bit [22]:bit[22] == 0
: Das NaN ist ein signalisierendes NaN. Das Vorzeichenbit kann einen beliebigen Wert annehmen, und die verbleibenden Bruchbits können einen beliebigen Wert außer allen Nullen annehmen.bit[22] == 1
: Das NaN ist ein leises NaN. Das Vorzeichenbit und die verbleibenden Bruchbits können einen beliebigen Wert annehmen.Wie werden qNanS und sNaNs generiert?
Ein Hauptunterschied zwischen qNaNs und sNaNs besteht darin, dass:
std::numeric_limits::signaling_NaN
Ich konnte keine eindeutigen IEEE 754- oder C11-Anführungszeichen dafür finden, aber ich kann auch keine eingebaute Operation finden, die sNaNs erzeugt ;-)
Das Intel-Handbuch gibt dieses Prinzip jedoch unter 4.8.3.4 "NaNs" deutlich an:
Dies ist aus unserem Beispiel ersichtlich, in dem beide:
produzieren genau die gleichen Bits wie
std::numeric_limits<float>::quiet_NaN()
.Beide Vorgänge werden zu einer einzigen x86-Assemblyanweisung kompiliert, die den qNaN direkt in der Hardware generiert (TODO-Bestätigung mit GDB).
Was machen qNaNs und sNaNs unterschiedlich?
Jetzt, da wir wissen, wie qNaNs und sNaNs aussehen und wie man sie manipuliert, sind wir endlich bereit, sNaNs dazu zu bringen, ihr Ding zu machen und einige Programme in die Luft zu jagen!
Also ohne weiteres:
blow_up.cpp
Kompilieren, ausführen und den Exit-Status erhalten:
Ausgabe:
Beachten Sie, dass dieses Verhalten nur
-O0
in GCC 8.2 auftritt: mit-O3
auftritt berechnet GCC alle unsere sNaN-Operationen vorab und optimiert sie! Ich bin mir nicht sicher, ob es eine standardkonforme Möglichkeit gibt, dies zu verhindern.Aus diesem Beispiel schließen wir also:
snan + 1.0
UrsachenFE_INVALID
, aberqnan + 1.0
nichtLinux erzeugt nur dann ein Signal, wenn es mit aktiviert ist
feenableexept
.Dies ist eine glibc-Erweiterung, ich konnte in keinem Standard einen Weg finden, dies zu tun.
Wenn das Signal auftritt, wird die CPU-Hardware selbst eine Ausnahme auslösen, die der Linux-Kernel behandelt und die Anwendung über das Signal informiert hat.
Das Ergebnis ist , dass bash druckt
Floating point exception (core dumped)
, und der Ausgangszustand ist136
, der dem entspricht , Signal136 - 128 == 8
, das gemäß:ist
SIGFPE
.Beachten Sie, dass dies
SIGFPE
dasselbe Signal ist, das wir erhalten, wenn wir versuchen, eine Ganzzahl durch 0 zu teilen:obwohl für ganze Zahlen:
feenableexcept
Wie gehe ich mit dem SIGFPE um?
Wenn Sie nur einen Handler erstellen, der normal zurückgibt, führt dies zu einer Endlosschleife, da die Division nach der Rückkehr des Handlers erneut erfolgt! Dies kann mit GDB überprüft werden.
Die einzige Möglichkeit besteht darin, wie folgt zu verwenden
setjmp
undlongjmp
an einen anderen Ort zu springen: C-Handle-Signal SIGFPE und Fortsetzung der AusführungWas sind einige reale Anwendungen von sNaNs?
Ganz ehrlich, ich habe immer noch keinen besonders nützlichen Anwendungsfall für sNaNs verstanden. Dies wurde gefragt unter: Nützlichkeit der Signalisierung von NaN?
sNaNs fühlen sich besonders nutzlos an, weil wir die anfänglich ungültigen Operationen (
0.0f/0.0f
) erkennen können, die qNaNs erzeugen mitfeenableexcept
: Es scheint, dasssnan
nur Fehler für weitere Operationen ausgelöst werden, die nicht ausgelöst werdenqnan
, z.qnan + 1.0f
. ).Z.B:
Haupt c
kompilieren:
dann:
gibt:
und:
gibt:
Siehe auch: So verfolgen Sie ein NaN in C ++
Was sind die Signalflags und wie werden sie manipuliert?
Alles ist in der CPU-Hardware implementiert.
Die Flags befinden sich in einem Register, ebenso wie das Bit, das angibt, ob eine Ausnahme / ein Signal ausgelöst werden soll.
Diese Register sind vom Benutzerland aus zugänglich von den meisten Bögen aus aus .
Dieser Teil des glibc 2.29-Codes ist eigentlich sehr einfach zu verstehen!
fetestexcept
Wird beispielsweise für x86_86 unter sysdeps / x86_64 / fpu / ftestexcept.c implementiert :so sehen wir sofort, dass die anweisungen verwendet werden
stmxcsr
für "Store MXCSR Register State" steht.Und
feenableexcept
ist implementiert unter sysdeps / x86_64 / fpu / feenablxcpt.c :Was sagt der C-Standard über qNaN vs sNaN?
Der Standardentwurf C11 N1570 besagt ausdrücklich, dass der Standard bei F.2.1 "Unendlichkeiten, vorzeichenbehaftete Nullen und NaNs" nicht zwischen ihnen unterscheidet:
Getestet in Ubuntu 18.10, GCC 8.2. GitHub Upstreams:
quelle