Nur eine Anmerkung, 1 Unze Vorbeugung ist besser als 1 Pfund Heilung. Mit anderen Worten, es ist weitaus besser zu verhindern , dass 0.f / 0.f jemals ausgeführt wird, als rückwirkend nach nan's in Ihrem Code zu suchen . nanDies kann für Ihr Programm furchtbar zerstörerisch sein. Wenn es sich vermehren darf, kann es schwer zu findende Fehler verursachen. Dies liegt daran, dass nanes giftig ist (5 * nan= nan), nannichts gleich ist ( nan! = nan), nanNicht größer als alles ( nan!> 0), nannicht kleiner als alles ( nan! <0) ist.
Bobobobo
1
@bobobobo: Dies ist eine Funktion, die eine zentralisierte Fehlerprüfung ermöglicht. Genau wie Ausnahmen gegen Rückgabewerte.
Ben Voigt
2
Warum hat <cmath> kein isnan ()? Es ist in std ::
frankliuao
Antworten:
349
Gemäß dem IEEE-Standard haben NaN-Werte die ungerade Eigenschaft, dass Vergleiche, an denen sie beteiligt sind, immer falsch sind. Das heißt, für einen Float f ist nur dann f != fwahr , wenn f NaN ist.
Beachten Sie, dass, wie einige Kommentare unten gezeigt haben, dies nicht alle Compiler bei der Optimierung von Code berücksichtigen.
Für jeden Compiler, die IEEE - Gleitkomma verwenden behauptet, dieser Trick sollte funktionieren. Aber ich kann nicht garantieren , dass es wird in der Praxis funktionieren. Fragen Sie im Zweifelsfall Ihren Compiler.
Der Compiler sollte dies besser nicht entfernen, wenn er in einem IEEE-Modus ausgeführt wird. Überprüfen Sie die Dokumentation für Ihren Compiler natürlich ...
dmckee --- Ex-Moderator Kätzchen
38
-1 funktioniert nur in der Theorie, nicht in der Praxis: Compiler wie g ++ (mit -fastmath) vermasseln das. Der einzige allgemeine Weg, bis c ++ 0x, besteht darin, auf Bitmuster zu testen.
Prost und hth. - Alf
66
@Alf: Die Dokumentation für die -ffast-mathOption besagt ausdrücklich, dass es zu einer falschen Ausgabe für Programme kommen kann, die von einer genauen Implementierung abhängen, wenn IEEE- oder ISO-Regeln / Spezifikationen für mathematische Funktionen vorliegen. Ohne diese Option ist die Verwendung x != xeine absolut gültige und tragbare Methode zum Testen von NaN.
Adam Rosenfield
7
@Adam: In der Dokumentation wird offen angegeben, dass sie nicht konform ist, ja. und ja, ich bin diesem Argument schon einmal begegnet und habe es ausführlich mit Gabriel Dos Reis besprochen. Es wird häufig verwendet, um das Design in einem Zirkelargument zu verteidigen (ich weiß nicht, ob Sie damit in Verbindung bringen wollten, aber es lohnt sich zu wissen - es ist Flammenkriegsmaterial). Ihre Schlussfolgerung, x != xdie ohne diese Option gültig ist, folgt nicht logisch. Dies kann für eine bestimmte Version von g ++ zutreffen oder nicht. Auf jeden Fall können Sie im Allgemeinen nicht garantieren, dass die Fastmath-Option nicht verwendet wird.
Prost und hth. - Alf
7
@Alf: Nein, ich war mir Ihrer Diskussion mit Gabriel Dos Reis nicht bewusst. Steve Jessop machte einen großen Punkt in der anderen Frage zur Annahme einer IEEE-Vertretung. Wenn Sie von IEEE 754 ausgehen und davon ausgehen, dass der Compiler konform arbeitet (dh ohne die -ffast-mathOption), x != xhandelt es sich um eine gültige und tragbare Lösung. Sie können sogar testen, -ffast-mathindem Sie das __FAST_MATH__Makro testen und in diesem Fall zu einer anderen Implementierung wechseln (z. B. Gewerkschaften und Bit-Twiddling verwenden).
Adam Rosenfield
220
isnan()In der aktuellen C ++ - Standardbibliothek ist keine Funktion verfügbar. Es wurde in C99 eingeführt und als Makro und nicht als Funktion definiert. Von C99 definierte Elemente der Standardbibliothek sind weder Teil des aktuellen C ++ - Standards ISO / IEC 14882: 1998 noch dessen Aktualisierung ISO / IEC 14882: 2003.
Im Jahr 2005 wurde der technische Bericht 1 vorgeschlagen. Der TR1 bringt Kompatibilität mit C99 zu C ++. Trotz der Tatsache, dass es nie offiziell als C ++ - Standard übernommen wurde, bieten viele ( GCC 4.0+ oder Visual C ++ 9.0+ C ++ - Implementierungen TR1-Funktionen, alle oder nur einige (Visual C ++ 9.0 bietet keine C99-Mathematikfunktionen). .
Wenn TR1 verfügbar ist, dann cmathschließt C99 Elemente wie isnan(), isfinite()usw. , aber sie sind als Funktionen definiert, keine Makros, in der Regel in std::tr1::Namespace, obwohl viele Implementierungen (dh GCC 4+ auf Linux oder in XCode auf Mac OS X 10.5 und höher) inject sie direkt zu std::, std::isnanist also gut definiert.
Darüber hinaus stellen einige Implementierungen von C ++ das C99- isnan()Makro weiterhin für C ++ zur Verfügung (enthalten durch cmathoder math.h), was zu weiteren Verwirrungen führen kann und Entwickler möglicherweise davon ausgehen, dass es sich um ein Standardverhalten handelt.
Ein Hinweis zu Viusal C ++, wie oben erwähnt, bietet std::isnanweder eine std::tr1::isnan, noch eine Erweiterungsfunktion, _isnan()die seit Visual C ++ 6.0 verfügbar ist
Auf XCode macht es noch mehr Spaß. Wie bereits erwähnt, definiert GCC 4+ std::isnan. Für ältere Versionen des Compilers und der Bibliothek von XCode scheinen (hier ist eine relevante Diskussion ), ich hatte keine Gelegenheit, mich selbst zu überprüfen) zwei Funktionen definiert worden, __inline_isnand()auf Intel und __isnand()auf Power PC.
Jeder möchte diese Funktionen wie isNan oder isInfinity. Warum nehmen die Verantwortlichen nicht einfach in ihre Standards auf? - Ich werde versuchen, herauszufinden, wie ich die Verantwortung übernehmen kann, und meine Stimme dafür abgeben. Ernsthaft.
Shuhalo
8
@shuhalo Schon verantwortlich?
Tomáš Zato - Wiedereinsetzung Monica
11
Diese Antwort sollte aktualisiert werden, da sie std::isnanjetzt Teil des C ++ 11-Standards ist und sich die Unterstützung ausgebreitet hat. std :: isnan wurde in Visual Studio ab Visual Studio 2013 implementiert. Vielleicht wurde @shuhalo verantwortlich :-)
aberaud
170
Erste Lösung: Wenn Sie C ++ 11 verwenden
Da dies gefragt wurde, gab es einige neue Entwicklungen: Es ist wichtig zu wissen, dass dies std::isnan()Teil von C ++ 11 ist
Bitte beachten Sie, dass dies nicht mit -fast-math kompatibel ist, wenn Sie g ++ verwenden. Weitere Vorschläge finden Sie weiter unten.
Andere Lösungen: Wenn Sie nicht C ++ 11-kompatible Tools verwenden
Für C99 wird dies in C als Makro implementiert isnan(c), das einen int-Wert zurückgibt. Die Art xmuss schwimmend, doppelt oder lang doppelt sein.
Verschiedene Anbieter können eine Funktion enthalten oder nicht isnan().
Die vermeintlich tragbare Methode zur Überprüfung NaNbesteht darin, die IEEE 754-Eigenschaft zu verwenden, NaNdie nicht gleich sich selbst ist: dh x == xfür das xSein falsch ist NaN.
Die letzte Option funktioniert jedoch möglicherweise nicht mit jedem Compiler und einigen Einstellungen (insbesondere Optimierungseinstellungen), sodass Sie als letztes Mittel immer das Bitmuster überprüfen können ...
Verdient definitiv die akzeptierte Antwort und verdient mehr positive Stimmen. Danke für den Tipp
LBes
3
−1std::isnan ist ab Februar 2017 immer noch eine schlechte Empfehlung, da es mit der Gleitkommaoptimierung von g ++ nicht funktioniert.
Prost und hth. - Alf
@ Cheersandhth.-Alf: Ist diese Option IEEE-konform? Die Antwort wurde bearbeitet
BlueTrin
@BlueTrin: Beide x != xund isnansind erforderlich, um die IEEE 754-Konformität zu gewährleisten . In Bezug auf letzteres besagt der IEEE 754-2008-Standard, dass „Implementierungen die folgenden nicht rechnerischen Operationen für alle unterstützten arithmetischen Formate bereitstellen müssen“ und „isNaN (x) genau dann wahr ist, wenn x ein NaN ist“. Zur Überprüfung der Konformität, die dieser Standard erfordert, is754version1985()und is754version2008()wo C ++ stattdessen bietet std::numeric_limits<Fp>::is_iec559()(IEC 559 ist der gleiche Standard). Leider -ffast-mathbeansprucht g ++ bei der Optimierung die Konformität, ist aber nicht konform.
Prost und hth. - Alf
1
Warnung: isnan (x) funktioniert nicht mit der Option -ffinite-math-only in gcc und clang
A Fog
82
In Boost gibt es auch eine Bibliothek nur für Header , die über nützliche Tools für den Umgang mit Gleitkomma-Datentypen verfügt
Es wurde in Boost 1.35 hinzugefügt (ich habe gerade festgestellt, dass mein Programm unter der alten Linux-Distribution nicht kompiliert werden kann).
Marcin
2
Wenn Sie mit der Option --fast-math kompilieren, funktioniert diese Funktion nicht wie erwartet.
Gaetano Mendola
43
Es gibt drei "offizielle" Möglichkeiten: posix isnan Makro , C ++ 0x- isnanFunktionsvorlage oder visuelle C ++ - _isnanFunktion .
Leider ist es ziemlich unpraktisch zu erkennen, welche davon verwendet werden sollen.
Und leider gibt es keine zuverlässige Möglichkeit, festzustellen, ob Sie eine IEEE 754-Darstellung mit NaNs haben. Die Standardbibliothek bietet einen offiziellen solchen Weg (numeric_limits<double>::is_iec559 ). Aber in der Praxis vermasseln Compiler wie g ++ das.
Theoretisch könnte man einfach verwenden x != x , aber Compiler wie g ++ und Visual C ++ vermasseln das.
Testen Sie also am Ende das Spezifische NaN-Bitmuster , indem Sie eine bestimmte Darstellung wie IEEE 754 annehmen (und hoffentlich irgendwann durchsetzen!).
BEARBEITEN : Als Beispiel für "Compiler wie g ++ ... vermasseln Sie das" betrachten Sie
#include<limits>#include<assert.h>void foo(double a,double b ){
assert( a != b );}int main(){typedef std::numeric_limits<double>Info;doubleconst nan1 =Info::quiet_NaN();doubleconst nan2 =Info::quiet_NaN();
foo( nan1, nan2 );}
Kompilieren mit g ++ (TDM-2 mingw32) 4.4.1:
C: \ test> Geben Sie "C: \ Programme \ @commands \ gnuc.bat" ein.
@rem -finput-charset = windows-1252
@ g ++ -O -pedantic -std = c ++ 98 -Wall -Wwrite-strings% * -Wno-long-long
C: \ test> gnuc x.cpp
C: \ test> ein && Echo funktioniert ... || Echo! fehlgeschlagen
funktioniert ...
C: \ test> gnuc x.cpp --fast-math
C: \ test> ein && Echo funktioniert ... || Echo! fehlgeschlagen
Behauptung fehlgeschlagen: a! = B, Datei x.cpp, Zeile 6
Diese Anwendung hat die Runtime aufgefordert, sie auf ungewöhnliche Weise zu beenden.
Bitte wenden Sie sich an das Support-Team der Anwendung, um weitere Informationen zu erhalten.
!gescheitert
C: \ test> _
@Alf: Ihr Beispiel funktioniert wie erwartet für mich unter Mac OS X und Linux unter verschiedenen Versionen von g ++ zwischen 4.0 und 4.5. Die Dokumentation für die -ffast-mathOption besagt ausdrücklich, dass es zu einer falschen Ausgabe für Programme kommen kann, die von einer genauen Implementierung abhängen, wenn IEEE- oder ISO-Regeln / Spezifikationen für mathematische Funktionen vorliegen. Ohne diese Option ist die Verwendung x != xeine absolut gültige und tragbare Methode zum Testen von NaN.
Adam Rosenfield
6
@Adam: Was Sie vermissen ist, dass der C ++ - Standard keine IEEE-Darstellung oder Mathematik für Floats erfordert. Soweit die Manpage Ihnen sagt, gcc -ffast-mathhandelt es sich immer noch um eine konforme C ++ - Implementierung (vorausgesetzt, es wird numeric_limits::is_iec559richtig, obwohl Alf dies oben nicht vorschlägt): C ++ - Code, der auf IEEE basiert, ist kein portables C ++ und hat kein Recht zu erwarten, dass Implementierungen es bereitstellen.
Steve Jessop
5
Und Alf hat recht, schneller Test auf gcc 4.3.4 und is_iec559ist wahr mit -ffast-math. Das Problem hierbei ist also, dass die GCC-Dokumente -ffast-mathnur sagen, dass es sich nicht um IEEE / ISO für mathematische Funktionen handelt, während sie sagen sollten , dass es sich nicht um C ++ handelt, da die Implementierung von nicht numeric_limitskorrekt ist. Ich würde vermuten, dass GCC zum Zeitpunkt der Definition der Vorlage nicht immer feststellen kann, ob das eventuelle Backend tatsächlich konforme Floats hat, und es daher nicht einmal versucht. IIRC gibt es ähnliche Probleme in der ausstehenden Fehlerliste für die C99-Konformität von GCC.
Steve Jessop
1
@Alf, @Steve, ich wusste nicht, dass der C ++ - Standard keine Spezifikation für Gleitkommawerte enthält. Es ist ziemlich schockierend für mich. Es sieht besser aus, IEEE 754 und NaN als plattformspezifische Erweiterung anstelle von Standard zu handhaben. Ist es nicht? Und kann ich erwarten, dass in C ++ 0x irgendeine Art von isnan () oder IEEE754 hinzugefügt wird?
Eonil
3
@Eonil: C ++ 0x hat zum Beispiel noch "Die Wertdarstellung von Fließkommatypen ist implementierungsdefiniert". C und C ++ zielen beide darauf ab, Implementierungen auf Computern ohne Gleitkomma-Hardware zu unterstützen, und ordnungsgemäße IEEE 754-Gleitkommazahlen können viel langsamer zu emulieren sein als einigermaßen genaue Alternativen. Die Theorie ist, dass Sie behaupten können, is_iec559wenn Sie IEEE benötigen, in der Praxis scheint dies bei GCC nicht zu funktionieren. C ++ 0x hat zwar eine isnanFunktion, aber da GCC is_iec559jetzt nicht korrekt implementiert wird , wird es -ffast-mathwahrscheinlich auch nicht in C ++ 0x funktionieren und möglicherweise seine Funktion beschädigen isnan.
Steve Jessop
39
Es gibt ein std :: isnan, wenn Ihr Compiler c99-Erweiterungen unterstützt, aber ich bin mir nicht sicher, ob mingw dies tut.
Hier ist eine kleine Funktion, die funktionieren sollte, wenn Ihr Compiler nicht über die Standardfunktion verfügt:
bool custom_isnan(double var){volatiledouble d = var;return d != d;}
Wenn dies der Fall ist, optimiert der Compiler den Vergleich und gibt immer true zurück.
CTT
23
Nein, gibt es nicht. Ein Compiler, der das macht, ist kaputt. Sie können auch sagen, dass die Standardbibliothek möglicherweise isnandas falsche Ergebnis zurückgibt. Technisch gesehen könnte der Compiler fehlerhaft sein, aber in der Praxis wird es nicht passieren. Gleich wie var != var. Es funktioniert, weil auf diese Weise IEEE-Gleitkommawerte definiert werden.
Jalf
29
Wenn -ffast-math gesetzt ist, gibt isnan () nicht das richtige Ergebnis für gcc zurück. Natürlich ist diese Optimierung als Verstoß gegen die IEEE-Semantik dokumentiert ...
Matthew Herrmann
Wenn -ffast-math gesetzt ist, ist der Compiler fehlerhaft. Oder besser gesagt, wenn -ffast-math eingestellt ist, sind alle Wetten geschlossen und Sie können sich sowieso nicht auf NaNs verlassen.
Adrian Ratnapala
25
Sie können numeric_limits<float>::quiet_NaN( )in der limitsStandardbibliothek definierte verwenden, um mit zu testen. Es ist eine separate Konstante für definiert double.
#include<iostream>#include<math.h>#include<limits>usingnamespace std;int main(){
cout <<"The quiet NaN for type float is: "<< numeric_limits<float>::quiet_NaN()<< endl;float f_nan = numeric_limits<float>::quiet_NaN();if( isnan(f_nan)){
cout <<"Float was Not a Number: "<< f_nan << endl;}return0;}
Ich weiß nicht, ob dies auf allen Plattformen funktioniert, da ich nur mit g ++ unter Linux getestet habe.
Beachten Sie jedoch, dass numeric_limits in GCC Version 3.2.3 einen Fehler enthält, da für quiet_NaN 0.0 zurückgegeben wird. Spätere Versionen von GCC sind meiner Erfahrung nach in Ordnung.
Nathan Kitchen
@ Nathan: Gut zu wissen. Ich benutze Version 4.3.2, also bin ich weit weg vom Wald.
Bill the Lizard
18
Sie können die isnan()Funktion verwenden, müssen jedoch die C-Mathematikbibliothek einschließen.
#include<cmath>
Da diese Funktion Teil von C99 ist, ist sie nicht überall verfügbar. Wenn Ihr Anbieter die Funktion nicht bereitstellt, können Sie aus Kompatibilitätsgründen auch Ihre eigene Variante definieren.
Ich habe <cmath> verwendet und es ist kein Isnan darin! übrigens fand ich heraus , dass es ist ein isnanin <math.h>
hasen
1
Wie gesagt, dies ist Teil von C99. Da C99 nicht Teil eines aktuellen C ++ - Standards ist, habe ich die Alternative bereitgestellt. Da es jedoch wahrscheinlich ist, dass isnan () in einem kommenden C ++ - Standard enthalten sein wird, habe ich eine # ifndef-Direktive um ihn herum eingefügt.
Raimue
12
Der folgende Code verwendet die Definition von NAN (alle Exponentenbits gesetzt, mindestens ein Teilbit gesetzt) und geht davon aus, dass sizeof (int) = sizeof (float) = 4. Sie können NAN in Wikipedia nach Details suchen.
Ich glaube, das würde auch auf Big-Endian-Plattformen funktionieren. Das Wörtliche 0x7fffffffwürde einfach in Erinnerung bleiben als ff ff ff 7f. valuehat die gleiche Reihenfolge wie es 0x7f800000, so dass alle Operationen in einer Reihe stehen (es gibt kein Austauschen von Bytes). Es würde mich interessieren, ob jemand dies auf einer Big-Endian-Plattform testen könnte.
Bryan W. Wagner
0x7fff1234ist auch ein NaN. So ist0xffffffff
Steve Hollasch
12
Nanoprävention
Meine Antwort auf diese Frage lautet: Verwenden Sie keine rückwirkenden Prüfungen fürnan . Verwenden Sie stattdessen vorbeugende Überprüfungen für Unterteilungen des Formulars 0.0/0.0.
#include<float.h>float x=0.f;// I'm gonna divide by x!if(!x )// Wait! Let me check if x is 0
x = FLT_MIN ;// oh, since x was 0, i'll just make it really small instead.float y =0.f/ x ;// whew, `nan` didn't appear.
nanErgebnisse aus der Operation 0.f/0.f, oder 0.0/0.0. nanist eine schreckliche Nemesis für die Stabilität Ihres Codes, die sehr sorgfältig erkannt und verhindert werden muss 1 . Die Eigenschaften davon nanunterscheiden sich von normalen Zahlen:
nanist giftig, (5 * nan= nan)
nanist nichts gleich, nicht einmal sich selbst ( nan! = nan)
nannicht größer als alles ( nan!> 0)
nanist nicht weniger als alles ( nan! <0)
Die letzten beiden aufgelisteten Eigenschaften sind gegenlogisch und führen zu einem merkwürdigen Verhalten des Codes, das auf Vergleichen mit einer nanZahl beruht (die drittletzte Eigenschaft ist ebenfalls ungerade, aber Sie werden sie wahrscheinlich nie x != x ?in Ihrem Code sehen (es sei denn, Sie überprüfen) für nan (unzuverlässig))).
In meinem eigenen Code habe ich festgestellt, dass nanWerte dazu neigen, schwer zu findende Fehler zu erzeugen. (Beachten Sie, dass dies bei oder nicht der Fall ist . ( <0) gibt zurück , (0 < ) gibt TRUE zurück und sogar ( < ) gibt TRUE zurück. Nach meiner Erfahrung ist das Verhalten des Codes daher häufig immer noch wie gewünscht.)inf-inf-infTRUEinf-infinf
was unter nan zu tun ist
Was unter passieren soll, 0.0/0.0muss als Sonderfall behandelt werden , aber was Sie tun, muss von den Zahlen abhängen, von denen Sie erwarten, dass sie aus dem Code hervorgehen.
Im obigen Beispiel ist das Ergebnis von ( 0.f/FLT_MIN) im 0Grunde genommen. Möglicherweise möchten Sie stattdessen 0.0/0.0generieren HUGE. Damit,
float x=0.f, y=0.f, z;if(!x &&!y )// 0.f/0.f case
z = FLT_MAX ;// biggest float possibleelse
z = y/x ;// regular division.
Wenn x oben wäre 0.f, infwürde sich dies ergeben (was ein ziemlich gutes / zerstörungsfreies Verhalten aufweist, wie oben tatsächlich erwähnt).
Denken Sie daran, dass die Ganzzahldivision durch 0 eine Laufzeitausnahme verursacht . Sie müssen also immer nach einer ganzzahligen Division durch 0 suchen. Nur weil 0.0/0.0leise ausgewertet wird, nanheißt das nicht, dass Sie faul sein können und nicht nachsehen müssen, 0.0/0.0bevor dies geschieht.
1 Überprüfungen auf nanVia x != xsind manchmal unzuverlässig ( x != xwerden von einigen optimierenden Compilern entfernt, die die IEEE-Konformität verletzen, insbesondere wenn der -ffast-mathSwitch aktiviert ist).
Vielen Dank für den Hinweis; Eine solche Programmierung würde definitiv bei dem Problem als solchem helfen. Versuchen Sie jedoch beim nächsten Mal, die Textformatierungsfunktionen nicht zu stark zu missbrauchen. Das Wechseln von Schriftgröße, Gewicht und Stil erschwert das Lesen.
Magnus
4
Beachten Sie, dass 0.0 / 0.0 nicht die einzige Operation ist, die zu einem NaN führen kann. Die Quadratwurzel einer negativen Zahl gibt NaN zurück. Der Cosinus von + unendlich gibt auch NaN zurück. Die Operation acos (x), bei der x nicht im Bereich [0, pi] liegt, kann ebenfalls zu NaN führen. Kurz gesagt, man muss besonders vorsichtig sein, um auch diese potenziell riskanten Operationen zu betrachten, nicht nur auf 0,0 / 0,0.
Boris Dalstein
Stimme Boris voll und ganz zu. Nach meiner Erfahrung stammte NaN praktisch immer von so etwas wie sqrt (-1.302e-53), dh Zwischenberechnungsergebnisse nahe Null, die in sqrt eingespeist wurden, ohne auf Negativität zu prüfen.
Hans_meine
1
"Verhindern von NaNs" bedeutet, dass Sie in alle grundlegenden arithmetischen Operationen einsteigen müssen, nicht nur in die Division. Sie müssen unter anderem auf ∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0 achten. Wenn Sie mit solchen grundlegenden Rechenoperationen "vorbeugend" sind, bedeutet dies, dass Sie Ihre Leistung vollständig verbessern (und wahrscheinlich zusätzliche Fälle verpassen, an die Sie nicht gedacht haben).
Steve Hollasch
11
Ab C ++ 14 gibt es verschiedene Möglichkeiten, um zu testen, ob eine Gleitkommazahl valueeine NaN ist.
Von diesen Möglichkeiten funktioniert nur die Überprüfung der Bits der Zahlendarstellung zuverlässig, wie in meiner ursprünglichen Antwort angegeben. Insbesondere std::isnanund die häufig vorgeschlagene Überprüfung v != vfunktionieren nicht zuverlässig und sollten nicht verwendet werden, damit Ihr Code nicht nicht mehr ordnungsgemäß funktioniert, wenn jemand entscheidet, dass eine Gleitkommaoptimierung erforderlich ist, und den Compiler dazu auffordert. Diese Situation kann sich ändern, Compiler können konformer werden, aber für dieses Problem ist dies in den 6 Jahren seit der ursprünglichen Antwort nicht aufgetreten.
Für ungefähr 6 Jahre war meine ursprüngliche Antwort die ausgewählte Lösung für diese Frage, die in Ordnung war. Vor kurzem wurde jedoch eine hoch gelobte Antwort ausgewählt, die den unzuverlässigen v != vTest empfiehlt . Daher diese zusätzliche aktuellere Antwort (wir haben jetzt die Standards C ++ 11 und C ++ 14 sowie C ++ 17 am Horizont).
Die wichtigsten Möglichkeiten zur Überprüfung der NaN-Fähigkeit ab C ++ 14 sind:
std::isnan(value) )
ist die beabsichtigte Standardbibliothek seit C ++ 11. isnanAnscheinend widerspricht es dem gleichnamigen Posix-Makro, aber in der Praxis ist das kein Problem. Das Hauptproblem besteht darin, dass, wenn eine Gleitkomma-Arithmetikoptimierung angefordert wird, mindestens ein Hauptcompiler, nämlich g ++, für das NaN-Argument std::isnanzurückgibtfalse .
(fpclassify(value) == FP_NAN) )
Leidet unter dem gleichen Problem std::isnan, dh ist nicht zuverlässig.
(value != value) )
Empfohlen in vielen SO-Antworten. Leidet unter dem gleichen Problem std::isnan, dh ist nicht zuverlässig.
(value == Fp_info::quiet_NaN()) )
Dies ist ein Test, der mit Standardverhalten keine NaNs erkennen sollte, der jedoch mit dem optimierten Verhalten möglicherweise NaNs erkennen könnte (aufgrund des optimierten Codes, der nur die Bitlevel-Darstellungen direkt vergleicht) und möglicherweise mit einer anderen Methode kombiniert wird, um das nicht optimierte Standardverhalten abzudecken konnte NaN zuverlässig nachweisen. Leider stellte sich heraus, dass es nicht zuverlässig funktionierte.
(ilogb(value) == FP_ILOGBNAN) )
Leidet unter dem gleichen Problem std::isnan, dh ist nicht zuverlässig.
isunordered(1.2345, value) )
Leidet unter dem gleichen Problem std::isnan, dh ist nicht zuverlässig.
is_ieee754_nan( value ) )
Dies ist keine Standardfunktion. Es überprüft die Bits gemäß dem IEEE 754-Standard. Es ist absolut zuverlässig, aber der Code ist etwas systemabhängig.
Im folgenden vollständigen Testcode ist „Erfolg“, ob ein Ausdruck die Nan-Ness des Werts meldet. Für die meisten Ausdrücke entspricht dieses Erfolgsmaß, das Ziel, NaNs und nur NaNs zu erkennen, ihrer Standardsemantik. Für den (value == Fp_info::quiet_NaN()) )Ausdruck ist das Standardverhalten jedoch, dass er nicht als NaN-Detektor funktioniert.
Ergebnisse mit g ++ (beachten Sie erneut, dass das Standardverhalten von (value == Fp_info::quiet_NaN())ist, dass es nicht als NaN-Detektor funktioniert, es ist hier nur sehr von praktischem Interesse):
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> g ++ --version | finde "++"
g ++ (x86_64-win32-sjlj-rev1, erstellt vom MinGW-W64-Projekt) 6.3.0
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> g ++ foo.cpp && a
Der Compiler behauptet, IEEE 754 = true
v = nan, (std :: isnan (Wert)) = wahrer Erfolg
u = 3,14, (std :: isnan (Wert)) = false Erfolg
w = inf, (std :: isnan (Wert)) = false Erfolg
v = nan, ((fpclassify (value) == 0x0100)) = true Erfolg
u = 3,14, ((fpclassify (Wert) == 0x0100)) = false Erfolg
w = inf, ((fpclassify (value) == 0x0100)) = false Erfolg
v = nan, ((Wert! = Wert)) = wahrer Erfolg
u = 3,14, ((Wert! = Wert)) = falsch Erfolg
w = inf, ((Wert! = Wert)) = false Erfolg
v = nan, ((Wert == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((Wert == Fp_info :: quiet_NaN ())) = false Erfolg
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Erfolg
v = nan, ((ilogb (Wert) == ((int) 0x80000000))) = wahr Erfolg
u = 3,14, ((ilogb (Wert) == ((int) 0x80000000))) = false Erfolg
w = inf, ((ilogb (Wert) == ((int) 0x80000000))) = false Erfolg
v = nan, (ungeordnet (1,2345, Wert)) = wahr Erfolg
u = 3,14, (ungeordnet (1,2345, Wert)) = falsch Erfolg
w = inf, (isunordered (1.2345, value)) = false Erfolg
v = nan, (is_ieee754_nan (Wert)) = true Erfolg
u = 3,14, (is_ieee754_nan (Wert)) = false Erfolg
w = inf, (is_ieee754_nan (Wert)) = false Erfolg
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> g ++ foo.cpp -ffast-math && a
Der Compiler behauptet, IEEE 754 = true
v = nan, (std :: isnan (Wert)) = false FAILED
u = 3,14, (std :: isnan (Wert)) = false Erfolg
w = inf, (std :: isnan (Wert)) = false Erfolg
v = nan, ((fpclassify (value) == 0x0100)) = false FAILED
u = 3,14, ((fpclassify (Wert) == 0x0100)) = false Erfolg
w = inf, ((fpclassify (value) == 0x0100)) = false Erfolg
v = nan, ((Wert! = Wert)) = false FAILED
u = 3,14, ((Wert! = Wert)) = falsch Erfolg
w = inf, ((Wert! = Wert)) = false Erfolg
v = nan, ((value == Fp_info :: quiet_NaN ())) = true Erfolg
u = 3.14, ((Wert == Fp_info :: quiet_NaN ())) = true FAILED
w = inf, ((value == Fp_info :: quiet_NaN ())) = true FAILED
v = nan, ((ilogb (Wert) == ((int) 0x80000000))) = wahr Erfolg
u = 3,14, ((ilogb (Wert) == ((int) 0x80000000))) = false Erfolg
w = inf, ((ilogb (Wert) == ((int) 0x80000000))) = false Erfolg
v = nan, (ungeordnet (1,2345, Wert)) = false FAILED
u = 3,14, (ungeordnet (1,2345, Wert)) = falsch Erfolg
w = inf, (isunordered (1.2345, value)) = false Erfolg
v = nan, (is_ieee754_nan (Wert)) = true Erfolg
u = 3,14, (is_ieee754_nan (Wert)) = false Erfolg
w = inf, (is_ieee754_nan (Wert)) = false Erfolg
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> _
Ergebnisse mit Visual C ++:
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> cl / nologo- 2> & 1 | finde "++"
Microsoft (R) C / C ++ - Optimierungscompiler Version 19.00.23725 für x86
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> cl foo.cpp / Feb && b
foo.cpp
Der Compiler behauptet, IEEE 754 = true
v = nan, (std :: isnan (Wert)) = wahrer Erfolg
u = 3,14, (std :: isnan (Wert)) = false Erfolg
w = inf, (std :: isnan (Wert)) = false Erfolg
v = nan, ((fpclassify (value) == 2)) = true Success
u = 3,14, ((fpclassify (Wert) == 2)) = false Erfolg
w = inf, ((fpclassify (value) == 2)) = false Erfolg
v = nan, ((Wert! = Wert)) = wahrer Erfolg
u = 3,14, ((Wert! = Wert)) = falsch Erfolg
w = inf, ((Wert! = Wert)) = false Erfolg
v = nan, ((Wert == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((Wert == Fp_info :: quiet_NaN ())) = false Erfolg
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Erfolg
v = nan, ((ilogb (Wert) == 0x7fffffff)) = wahr Erfolg
u = 3,14, ((ilogb (Wert) == 0x7fffffff)) = false Erfolg
w = inf, ((ilogb (Wert) == 0x7fffffff)) = true FAILED
v = nan, (ungeordnet (1,2345, Wert)) = wahr Erfolg
u = 3,14, (ungeordnet (1,2345, Wert)) = falsch Erfolg
w = inf, (isunordered (1.2345, value)) = false Erfolg
v = nan, (is_ieee754_nan (Wert)) = true Erfolg
u = 3,14, (is_ieee754_nan (Wert)) = false Erfolg
w = inf, (is_ieee754_nan (Wert)) = false Erfolg
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> cl foo.cpp / Feb / fp: schnell && b
foo.cpp
Der Compiler behauptet, IEEE 754 = true
v = nan, (std :: isnan (Wert)) = wahrer Erfolg
u = 3,14, (std :: isnan (Wert)) = false Erfolg
w = inf, (std :: isnan (Wert)) = false Erfolg
v = nan, ((fpclassify (value) == 2)) = true Success
u = 3,14, ((fpclassify (Wert) == 2)) = false Erfolg
w = inf, ((fpclassify (value) == 2)) = false Erfolg
v = nan, ((Wert! = Wert)) = wahrer Erfolg
u = 3,14, ((Wert! = Wert)) = falsch Erfolg
w = inf, ((Wert! = Wert)) = false Erfolg
v = nan, ((Wert == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((Wert == Fp_info :: quiet_NaN ())) = false Erfolg
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Erfolg
v = nan, ((ilogb (Wert) == 0x7fffffff)) = wahr Erfolg
u = 3,14, ((ilogb (Wert) == 0x7fffffff)) = false Erfolg
w = inf, ((ilogb (Wert) == 0x7fffffff)) = true FAILED
v = nan, (ungeordnet (1,2345, Wert)) = wahr Erfolg
u = 3,14, (ungeordnet (1,2345, Wert)) = falsch Erfolg
w = inf, (isunordered (1.2345, value)) = false Erfolg
v = nan, (is_ieee754_nan (Wert)) = true Erfolg
u = 3,14, (is_ieee754_nan (Wert)) = false Erfolg
w = inf, (is_ieee754_nan (Wert)) = false Erfolg
[C: \ my \ forums \ so \ 282 (NaN erkennen)]
> _
Zusammenfassend lässt sich sagen, dass nur das direkte Testen der Darstellung auf Bitebene mit der is_ieee754_nanin diesem Testprogramm definierten Funktion in allen Fällen sowohl mit g ++ als auch mit Visual C ++ zuverlässig funktionierte.
Nachtrag:
Nachdem ich das oben Gesagte veröffentlicht hatte, wurde mir ein weiterer möglicher Test für NaN bewusst, der in einer anderen Antwort hier erwähnt wurde, nämlich ((value < 0) == (value >= 0)). Das hat mit Visual C ++ gut funktioniert, ist aber mit der -ffast-mathOption von g ++ fehlgeschlagen . Nur direkte Bitmusterprüfungen funktionieren zuverlässig.
inlineboolIsNan(float f){constuint32 u =*(uint32*)&f;return(u&0x7F800000)==0x7F800000&&(u&0x7FFFFF);// Both NaN and qNan.}inlineboolIsNan(double d){constuint64 u =*(uint64*)&d;return(u&0x7FF0000000000000ULL)==0x7FF0000000000000ULL&&(u&0xFFFFFFFFFFFFFULL);}
Dies funktioniert, wenn sizeof(int)4 und sizeof(long long)8 ist.
Während der Laufzeit ist es nur ein Vergleich, Gussteile brauchen keine Zeit. Es wird lediglich die Konfiguration der Vergleichsflags geändert, um die Gleichheit zu überprüfen.
Beachten Sie auch, dass es auf die IEEE 754-Darstellung beschränkt ist.
Prost und hth. - Alf
Beachten Sie, dass diese Umwandlung gegen die strenge Aliasing-Regel von g ++ verstößt und dass der Compiler bekanntermaßen Unmentionable Things ™ ausführt, wenn er formale UB erkennt. Anstelle effizienter Casts müssen Sie mit g ++ sicherheitshalber memcpyein Byte-Array verwenden. Code dafür in meiner Antwort Nr. 2 .
Prost und hth. - Alf
4
Eine mögliche Lösung, die nicht von der spezifischen IEEE-Darstellung für verwendetes NaN abhängt, wäre die folgende:
template<class T>bool isnan( T f ){
T _nan =(T)0.0/(T)0.0;return0== memcmp((void*)&f,(void*)&_nan,sizeof(T));}
Gleitkommazahlen mit einfacher Genauigkeit haben über 8 Millionen legitime und unterschiedliche Bitrepräsentationen für NaN, sodass Sie weitere Vergleiche hinzufügen müssen. :)
Steve Hollasch
4
In Anbetracht dessen, dass (x! = X) für NaN nicht immer garantiert ist (z. B. wenn die Option -ffast-math verwendet wird), habe ich Folgendes verwendet:
#define IS_NAN(x)(((x)<0)==((x)>=0))
Zahlen können nicht sowohl <0 als auch> = 0 sein, daher besteht diese Prüfung nur dann, wenn die Zahl weder kleiner noch größer oder gleich Null ist. Welches ist im Grunde überhaupt keine Zahl oder NaN.
Sie können dies auch verwenden, wenn Sie Folgendes bevorzugen:
#define IS_NAN(x)(!((x)<0)&&!((x)>=0)
Ich bin mir nicht sicher, wie sich dies auf -ffast-math auswirkt, daher kann Ihr Kilometerstand variieren.
Dies ist tatsächlich fehlerhaft, genauso wie f != fes auch fehlerhaft ist. Ich habe gesehen, wie llvm einen fast identischen Code entfernt hat. Der Optimierer kann die Informationen über den ersten Vergleich weitergeben und herausfinden, dass der zweite Vergleich möglicherweise niemals wahr ist, wenn der erste Vergleich vorliegt. (Wenn der Compiler die IEEE-Regeln strikt einhält, f != fist das sowieso viel einfacher)
Für mich könnte die Lösung ein Makro sein, um es explizit inline und damit schnell genug zu machen. Es funktioniert auch für jeden Float-Typ. Es basiert auf der Tatsache, dass der einzige Fall, in dem ein Wert nicht gleich ist, der ist, wenn der Wert keine Zahl ist.
Es scheint mir, dass der beste wirklich plattformübergreifende Ansatz darin besteht, eine Union zu verwenden und das Bitmuster des Double zu testen, um nach NaNs zu suchen.
Ich habe diese Lösung nicht gründlich getestet, und es gibt möglicherweise eine effizientere Möglichkeit, mit den Bitmustern zu arbeiten, aber ich denke, dass sie funktionieren sollte.
#include<stdint.h>#include<stdio.h>unionNaN{uint64_t bits;double num;};int main(){//Test if a double is NaNdouble d =0.0/0.0;unionNaN n;
n.num = d;if((n.bits |0x800FFFFFFFFFFFFF)==0xFFFFFFFFFFFFFFFF){
printf("NaN: %f", d);}return0;}
Beachten Sie, dass "es ein undefiniertes Verhalten ist, von einem Gewerkschaftsmitglied zu lesen, das zuletzt nicht geschrieben wurde". Daher unionfunktioniert diese Verwendung eines Wortspiels zwischen zwei Typen möglicherweise nicht wie gewünscht (: sad_panda :). Der richtige (wenn auch nicht ganz so portable wie gewünschte) Weg wäre, die Vereinigung insgesamt zu vermeiden und ein Memcpy von der doublein eine andere uint64_tVariable durchzuführen und dann den Test mit dieser Hilfsvariablen durchzuführen.
Eljay
0
Unter x86-64 können Sie extrem schnelle Methoden zum Überprüfen auf NaN und unendlich verwenden, die unabhängig von der -ffast-mathCompileroption funktionieren . ( f != f, std::isnan, std::isinfErgeben immer falsemit -ffast-math).
Das Testen auf NaN, unendlich und endliche Zahlen kann leicht durchgeführt werden, indem nach dem maximalen Exponenten gesucht wird. unendlich ist maximaler Exponent mit Mantisse Null, NaN ist maximaler Exponent und Mantisse ungleich Null. Der Exponent wird in den nächsten Bits nach dem obersten Vorzeichenbit gespeichert, so dass wir die Verschiebung nach links verschieben können, um das Vorzeichenbit zu entfernen und den Exponenten zu den obersten Bits zu machen. Es ist keine Maskierung ( operator&) erforderlich:
staticinlineuint64_t load_ieee754_rep(double a){uint64_t r;static_assert(sizeof r ==sizeof a,"Unexpected sizes.");
std::memcpy(&r,&a,sizeof a);// Generates movq instruction.return r;}staticinlineuint32_t load_ieee754_rep(float a){uint32_t r;static_assert(sizeof r ==sizeof a,"Unexpected sizes.");
std::memcpy(&r,&a,sizeof a);// Generates movd instruction.return r;}constexpruint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);constexpruint32_t inf_float_shl1 = UINT32_C(0xff000000);// The shift left removes the sign bit. The exponent moves into the topmost bits,// so that plain unsigned comparison is enough.staticinlinebool isnan2(double a){return load_ieee754_rep(a)<<1> inf_double_shl1;}staticinlinebool isinf2(double a){return load_ieee754_rep(a)<<1== inf_double_shl1;}staticinlinebool isfinite2(double a){return load_ieee754_rep(a)<<1< inf_double_shl1;}staticinlinebool isnan2(float a){return load_ieee754_rep(a)<<1> inf_float_shl1;}staticinlinebool isinf2(float a){return load_ieee754_rep(a)<<1== inf_float_shl1;}staticinlinebool isfinite2(float a){return load_ieee754_rep(a)<<1< inf_float_shl1;}
Die stdVersionen von isinfund isfiniteLaden von 2 double/floatKonstanten aus dem .dataSegment und im schlimmsten Fall können sie 2 Datencache-Fehler verursachen. Die obigen Versionen laden keine Daten inf_double_shl1und inf_float_shl1Konstanten werden als unmittelbare Operanden in die Montageanweisungen codiert.
Verwendet die Tatsache, dass der ucomisdBefehl das Paritätsflag setzt, wenn ein Argument NaN ist. So std::isnanfunktioniert es, wenn keine -ffast-mathOptionen angegeben sind.
Der IEEE-Standard sagt, wenn der Exponent alle 1s ist und die Mantisse nicht Null ist, ist die Zahl a NaN. Double ist 1Vorzeichenbit, 11Exponentenbit und 52Mantissenbit. Mach ein bisschen nach.
Wie oben erwähnt, funktioniert a! = A in g ++ und einigen anderen Compilern nicht, aber dieser Trick sollte. Es mag nicht so effizient sein, aber es ist immer noch ein Weg:
Grundsätzlich druckt printf in g ++ (bei anderen bin ich mir allerdings nicht sicher) 'nan' in den Formaten% d oder% .f, wenn die Variable keine gültige Ganzzahl / float ist. Daher prüft dieser Code, ob das erste Zeichen der Zeichenfolge 'n' ist (wie in "nan").
Würde das nicht einen Pufferüberlauf verursachen, wenn a = 234324.0f?
Mazyod
Ja, oder 340282346638528859811704183484516925440.000wenn a = FLT_MAX. Er müsste verwenden char s[7]; sprintf(s, "%.0g", a);, das werden 6 chrs sein, wenn a=-FLT_MAX, oder-3e+38
Bobobobo
-3
Dies erkennt Unendlichkeit und auch NaN in Visual Studio, indem überprüft wird, ob es innerhalb doppelter Grenzen liegt:
//#include <float.h>double x, y =-1.1; x = sqrt(y);if(x >= DBL_MIN && x <= DBL_MAX )
cout <<"DETECTOR-2 of errors FAILS"<< endl;else
cout <<"DETECTOR-2 of errors OK"<< endl;
Überprüfen Sie die Definition von FLT_MIN, DBL_MINund LDBL_MINvorsichtiger. Dies sind die kleinsten normalisierten Werte für jeden Typ. Beispielsweise hat die einfache Genauigkeit über 8 Millionen legitime Denorm-Werte, die größer als Null und kleiner als FLT_MIN(und nicht NaN) sind.
nan
's in Ihrem Code zu suchen .nan
Dies kann für Ihr Programm furchtbar zerstörerisch sein. Wenn es sich vermehren darf, kann es schwer zu findende Fehler verursachen. Dies liegt daran, dassnan
es giftig ist (5 *nan
=nan
),nan
nichts gleich ist (nan
! =nan
),nan
Nicht größer als alles (nan
!> 0),nan
nicht kleiner als alles (nan
! <0) ist.Antworten:
Gemäß dem IEEE-Standard haben NaN-Werte die ungerade Eigenschaft, dass Vergleiche, an denen sie beteiligt sind, immer falsch sind. Das heißt, für einen Float f ist nur dann
f != f
wahr , wenn f NaN ist.Beachten Sie, dass, wie einige Kommentare unten gezeigt haben, dies nicht alle Compiler bei der Optimierung von Code berücksichtigen.
Für jeden Compiler, die IEEE - Gleitkomma verwenden behauptet, dieser Trick sollte funktionieren. Aber ich kann nicht garantieren , dass es wird in der Praxis funktionieren. Fragen Sie im Zweifelsfall Ihren Compiler.
quelle
-ffast-math
Option besagt ausdrücklich, dass es zu einer falschen Ausgabe für Programme kommen kann, die von einer genauen Implementierung abhängen, wenn IEEE- oder ISO-Regeln / Spezifikationen für mathematische Funktionen vorliegen. Ohne diese Option ist die Verwendungx != x
eine absolut gültige und tragbare Methode zum Testen von NaN.x != x
die ohne diese Option gültig ist, folgt nicht logisch. Dies kann für eine bestimmte Version von g ++ zutreffen oder nicht. Auf jeden Fall können Sie im Allgemeinen nicht garantieren, dass die Fastmath-Option nicht verwendet wird.-ffast-math
Option),x != x
handelt es sich um eine gültige und tragbare Lösung. Sie können sogar testen,-ffast-math
indem Sie das__FAST_MATH__
Makro testen und in diesem Fall zu einer anderen Implementierung wechseln (z. B. Gewerkschaften und Bit-Twiddling verwenden).isnan()
In der aktuellen C ++ - Standardbibliothek ist keine Funktion verfügbar. Es wurde in C99 eingeführt und als Makro und nicht als Funktion definiert. Von C99 definierte Elemente der Standardbibliothek sind weder Teil des aktuellen C ++ - Standards ISO / IEC 14882: 1998 noch dessen Aktualisierung ISO / IEC 14882: 2003.Im Jahr 2005 wurde der technische Bericht 1 vorgeschlagen. Der TR1 bringt Kompatibilität mit C99 zu C ++. Trotz der Tatsache, dass es nie offiziell als C ++ - Standard übernommen wurde, bieten viele ( GCC 4.0+ oder Visual C ++ 9.0+ C ++ - Implementierungen TR1-Funktionen, alle oder nur einige (Visual C ++ 9.0 bietet keine C99-Mathematikfunktionen). .
Wenn TR1 verfügbar ist, dann
cmath
schließt C99 Elemente wieisnan()
,isfinite()
usw. , aber sie sind als Funktionen definiert, keine Makros, in der Regel instd::tr1::
Namespace, obwohl viele Implementierungen (dh GCC 4+ auf Linux oder in XCode auf Mac OS X 10.5 und höher) inject sie direkt zustd::
,std::isnan
ist also gut definiert.Darüber hinaus stellen einige Implementierungen von C ++ das C99-
isnan()
Makro weiterhin für C ++ zur Verfügung (enthalten durchcmath
odermath.h
), was zu weiteren Verwirrungen führen kann und Entwickler möglicherweise davon ausgehen, dass es sich um ein Standardverhalten handelt.Ein Hinweis zu Viusal C ++, wie oben erwähnt, bietet
std::isnan
weder einestd::tr1::isnan
, noch eine Erweiterungsfunktion,_isnan()
die seit Visual C ++ 6.0 verfügbar istAuf XCode macht es noch mehr Spaß. Wie bereits erwähnt, definiert GCC 4+
std::isnan
. Für ältere Versionen des Compilers und der Bibliothek von XCode scheinen (hier ist eine relevante Diskussion ), ich hatte keine Gelegenheit, mich selbst zu überprüfen) zwei Funktionen definiert worden,__inline_isnand()
auf Intel und__isnand()
auf Power PC.quelle
std::isnan
jetzt Teil des C ++ 11-Standards ist und sich die Unterstützung ausgebreitet hat. std :: isnan wurde in Visual Studio ab Visual Studio 2013 implementiert. Vielleicht wurde @shuhalo verantwortlich :-)Erste Lösung: Wenn Sie C ++ 11 verwenden
Da dies gefragt wurde, gab es einige neue Entwicklungen: Es ist wichtig zu wissen, dass dies
std::isnan()
Teil von C ++ 11 istZusammenfassung
In der Kopfzeile definiert
<cmath>
Bestimmt, ob die angegebene Gleitkommazahl arg keine Zahl ist (
NaN
).Parameter
arg
: GleitkommawertRückgabewert
true
wenn arg istNaN
,false
sonstReferenz
http://en.cppreference.com/w/cpp/numeric/math/isnan
Bitte beachten Sie, dass dies nicht mit -fast-math kompatibel ist, wenn Sie g ++ verwenden. Weitere Vorschläge finden Sie weiter unten.
Andere Lösungen: Wenn Sie nicht C ++ 11-kompatible Tools verwenden
Für C99 wird dies in C als Makro implementiert
isnan(c)
, das einen int-Wert zurückgibt. Die Artx
muss schwimmend, doppelt oder lang doppelt sein.Verschiedene Anbieter können eine Funktion enthalten oder nicht
isnan()
.Die vermeintlich tragbare Methode zur Überprüfung
NaN
besteht darin, die IEEE 754-Eigenschaft zu verwenden,NaN
die nicht gleich sich selbst ist: dhx == x
für dasx
Sein falsch istNaN
.Die letzte Option funktioniert jedoch möglicherweise nicht mit jedem Compiler und einigen Einstellungen (insbesondere Optimierungseinstellungen), sodass Sie als letztes Mittel immer das Bitmuster überprüfen können ...
quelle
std::isnan
ist ab Februar 2017 immer noch eine schlechte Empfehlung, da es mit der Gleitkommaoptimierung von g ++ nicht funktioniert.x != x
undisnan
sind erforderlich, um die IEEE 754-Konformität zu gewährleisten . In Bezug auf letzteres besagt der IEEE 754-2008-Standard, dass „Implementierungen die folgenden nicht rechnerischen Operationen für alle unterstützten arithmetischen Formate bereitstellen müssen“ und „isNaN (x) genau dann wahr ist, wenn x ein NaN ist“. Zur Überprüfung der Konformität, die dieser Standard erfordert,is754version1985()
undis754version2008()
wo C ++ stattdessen bietetstd::numeric_limits<Fp>::is_iec559()
(IEC 559 ist der gleiche Standard). Leider-ffast-math
beansprucht g ++ bei der Optimierung die Konformität, ist aber nicht konform.In Boost gibt es auch eine Bibliothek nur für Header , die über nützliche Tools für den Umgang mit Gleitkomma-Datentypen verfügt
Sie erhalten folgende Funktionen:
Wenn Sie Zeit haben, schauen Sie sich das gesamte Math-Toolkit von Boost an. Es enthält viele nützliche Tools und wächst schnell.
Auch beim Umgang mit Gleitkomma- und Nicht-Gleitkommawerten ist es möglicherweise eine gute Idee, sich die numerischen Konvertierungen anzusehen .
quelle
Es gibt drei "offizielle" Möglichkeiten: posix
isnan
Makro , C ++ 0x-isnan
Funktionsvorlage oder visuelle C ++ -_isnan
Funktion .Leider ist es ziemlich unpraktisch zu erkennen, welche davon verwendet werden sollen.
Und leider gibt es keine zuverlässige Möglichkeit, festzustellen, ob Sie eine IEEE 754-Darstellung mit NaNs haben. Die Standardbibliothek bietet einen offiziellen solchen Weg (
numeric_limits<double>::is_iec559
). Aber in der Praxis vermasseln Compiler wie g ++ das.Theoretisch könnte man einfach verwenden
x != x
, aber Compiler wie g ++ und Visual C ++ vermasseln das.Testen Sie also am Ende das Spezifische NaN-Bitmuster , indem Sie eine bestimmte Darstellung wie IEEE 754 annehmen (und hoffentlich irgendwann durchsetzen!).
BEARBEITEN : Als Beispiel für "Compiler wie g ++ ... vermasseln Sie das" betrachten Sie
Kompilieren mit g ++ (TDM-2 mingw32) 4.4.1:
quelle
-ffast-math
Option besagt ausdrücklich, dass es zu einer falschen Ausgabe für Programme kommen kann, die von einer genauen Implementierung abhängen, wenn IEEE- oder ISO-Regeln / Spezifikationen für mathematische Funktionen vorliegen. Ohne diese Option ist die Verwendungx != x
eine absolut gültige und tragbare Methode zum Testen von NaN.gcc -ffast-math
handelt es sich immer noch um eine konforme C ++ - Implementierung (vorausgesetzt, es wirdnumeric_limits::is_iec559
richtig, obwohl Alf dies oben nicht vorschlägt): C ++ - Code, der auf IEEE basiert, ist kein portables C ++ und hat kein Recht zu erwarten, dass Implementierungen es bereitstellen.is_iec559
ist wahr mit-ffast-math
. Das Problem hierbei ist also, dass die GCC-Dokumente-ffast-math
nur sagen, dass es sich nicht um IEEE / ISO für mathematische Funktionen handelt, während sie sagen sollten , dass es sich nicht um C ++ handelt, da die Implementierung von nichtnumeric_limits
korrekt ist. Ich würde vermuten, dass GCC zum Zeitpunkt der Definition der Vorlage nicht immer feststellen kann, ob das eventuelle Backend tatsächlich konforme Floats hat, und es daher nicht einmal versucht. IIRC gibt es ähnliche Probleme in der ausstehenden Fehlerliste für die C99-Konformität von GCC.is_iec559
wenn Sie IEEE benötigen, in der Praxis scheint dies bei GCC nicht zu funktionieren. C ++ 0x hat zwar eineisnan
Funktion, aber da GCCis_iec559
jetzt nicht korrekt implementiert wird , wird es-ffast-math
wahrscheinlich auch nicht in C ++ 0x funktionieren und möglicherweise seine Funktion beschädigenisnan
.Es gibt ein std :: isnan, wenn Ihr Compiler c99-Erweiterungen unterstützt, aber ich bin mir nicht sicher, ob mingw dies tut.
Hier ist eine kleine Funktion, die funktionieren sollte, wenn Ihr Compiler nicht über die Standardfunktion verfügt:
quelle
isnan
das falsche Ergebnis zurückgibt. Technisch gesehen könnte der Compiler fehlerhaft sein, aber in der Praxis wird es nicht passieren. Gleich wievar != var
. Es funktioniert, weil auf diese Weise IEEE-Gleitkommawerte definiert werden.Sie können
numeric_limits<float>::quiet_NaN( )
in derlimits
Standardbibliothek definierte verwenden, um mit zu testen. Es ist eine separate Konstante für definiertdouble
.Ich weiß nicht, ob dies auf allen Plattformen funktioniert, da ich nur mit g ++ unter Linux getestet habe.
quelle
Sie können die
isnan()
Funktion verwenden, müssen jedoch die C-Mathematikbibliothek einschließen.Da diese Funktion Teil von C99 ist, ist sie nicht überall verfügbar. Wenn Ihr Anbieter die Funktion nicht bereitstellt, können Sie aus Kompatibilitätsgründen auch Ihre eigene Variante definieren.
quelle
isnan
in <math.h>Der folgende Code verwendet die Definition von NAN (alle Exponentenbits gesetzt, mindestens ein Teilbit gesetzt) und geht davon aus, dass sizeof (int) = sizeof (float) = 4. Sie können NAN in Wikipedia nach Details suchen.
bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }
quelle
0x7fffffff
würde einfach in Erinnerung bleiben alsff ff ff 7f
.value
hat die gleiche Reihenfolge wie es0x7f800000
, so dass alle Operationen in einer Reihe stehen (es gibt kein Austauschen von Bytes). Es würde mich interessieren, ob jemand dies auf einer Big-Endian-Plattform testen könnte.0x7fff1234
ist auch ein NaN. So ist0xffffffff
Nanoprävention
Meine Antwort auf diese Frage lautet: Verwenden Sie keine rückwirkenden Prüfungen für
nan
. Verwenden Sie stattdessen vorbeugende Überprüfungen für Unterteilungen des Formulars0.0/0.0
.nan
Ergebnisse aus der Operation0.f/0.f
, oder0.0/0.0
.nan
ist eine schreckliche Nemesis für die Stabilität Ihres Codes, die sehr sorgfältig erkannt und verhindert werden muss 1 . Die Eigenschaften davonnan
unterscheiden sich von normalen Zahlen:nan
ist giftig, (5 *nan
=nan
)nan
ist nichts gleich, nicht einmal sich selbst (nan
! =nan
)nan
nicht größer als alles (nan
!> 0)nan
ist nicht weniger als alles (nan
! <0)Die letzten beiden aufgelisteten Eigenschaften sind gegenlogisch und führen zu einem merkwürdigen Verhalten des Codes, das auf Vergleichen mit einer
nan
Zahl beruht (die drittletzte Eigenschaft ist ebenfalls ungerade, aber Sie werden sie wahrscheinlich niex != x ?
in Ihrem Code sehen (es sei denn, Sie überprüfen) für nan (unzuverlässig))).In meinem eigenen Code habe ich festgestellt, dass
nan
Werte dazu neigen, schwer zu findende Fehler zu erzeugen. (Beachten Sie, dass dies bei oder nicht der Fall ist . ( <0) gibt zurück , (0 < ) gibt TRUE zurück und sogar ( < ) gibt TRUE zurück. Nach meiner Erfahrung ist das Verhalten des Codes daher häufig immer noch wie gewünscht.)inf
-inf
-inf
TRUE
inf
-inf
inf
was unter nan zu tun ist
Was unter passieren soll,
0.0/0.0
muss als Sonderfall behandelt werden , aber was Sie tun, muss von den Zahlen abhängen, von denen Sie erwarten, dass sie aus dem Code hervorgehen.Im obigen Beispiel ist das Ergebnis von (
0.f/FLT_MIN
) im0
Grunde genommen. Möglicherweise möchten Sie stattdessen0.0/0.0
generierenHUGE
. Damit,Wenn x oben wäre
0.f
,inf
würde sich dies ergeben (was ein ziemlich gutes / zerstörungsfreies Verhalten aufweist, wie oben tatsächlich erwähnt).Denken Sie daran, dass die Ganzzahldivision durch 0 eine Laufzeitausnahme verursacht . Sie müssen also immer nach einer ganzzahligen Division durch 0 suchen. Nur weil
0.0/0.0
leise ausgewertet wird,nan
heißt das nicht, dass Sie faul sein können und nicht nachsehen müssen,0.0/0.0
bevor dies geschieht.1 Überprüfungen auf
nan
Viax != x
sind manchmal unzuverlässig (x != x
werden von einigen optimierenden Compilern entfernt, die die IEEE-Konformität verletzen, insbesondere wenn der-ffast-math
Switch aktiviert ist).quelle
Ab C ++ 14 gibt es verschiedene Möglichkeiten, um zu testen, ob eine Gleitkommazahl
value
eine NaN ist.Von diesen Möglichkeiten funktioniert nur die Überprüfung der Bits der Zahlendarstellung zuverlässig, wie in meiner ursprünglichen Antwort angegeben. Insbesondere
std::isnan
und die häufig vorgeschlagene Überprüfungv != v
funktionieren nicht zuverlässig und sollten nicht verwendet werden, damit Ihr Code nicht nicht mehr ordnungsgemäß funktioniert, wenn jemand entscheidet, dass eine Gleitkommaoptimierung erforderlich ist, und den Compiler dazu auffordert. Diese Situation kann sich ändern, Compiler können konformer werden, aber für dieses Problem ist dies in den 6 Jahren seit der ursprünglichen Antwort nicht aufgetreten.Für ungefähr 6 Jahre war meine ursprüngliche Antwort die ausgewählte Lösung für diese Frage, die in Ordnung war. Vor kurzem wurde jedoch eine hoch gelobte Antwort ausgewählt, die den unzuverlässigen
v != v
Test empfiehlt . Daher diese zusätzliche aktuellere Antwort (wir haben jetzt die Standards C ++ 11 und C ++ 14 sowie C ++ 17 am Horizont).Die wichtigsten Möglichkeiten zur Überprüfung der NaN-Fähigkeit ab C ++ 14 sind:
std::isnan(value) )
ist die beabsichtigte Standardbibliothek seit C ++ 11.
isnan
Anscheinend widerspricht es dem gleichnamigen Posix-Makro, aber in der Praxis ist das kein Problem. Das Hauptproblem besteht darin, dass, wenn eine Gleitkomma-Arithmetikoptimierung angefordert wird, mindestens ein Hauptcompiler, nämlich g ++, für das NaN-Argumentstd::isnan
zurückgibtfalse
.(fpclassify(value) == FP_NAN) )
Leidet unter dem gleichen Problem
std::isnan
, dh ist nicht zuverlässig.(value != value) )
Empfohlen in vielen SO-Antworten. Leidet unter dem gleichen Problem
std::isnan
, dh ist nicht zuverlässig.(value == Fp_info::quiet_NaN()) )
Dies ist ein Test, der mit Standardverhalten keine NaNs erkennen sollte, der jedoch mit dem optimierten Verhalten möglicherweise NaNs erkennen könnte (aufgrund des optimierten Codes, der nur die Bitlevel-Darstellungen direkt vergleicht) und möglicherweise mit einer anderen Methode kombiniert wird, um das nicht optimierte Standardverhalten abzudecken konnte NaN zuverlässig nachweisen. Leider stellte sich heraus, dass es nicht zuverlässig funktionierte.
(ilogb(value) == FP_ILOGBNAN) )
Leidet unter dem gleichen Problem
std::isnan
, dh ist nicht zuverlässig.isunordered(1.2345, value) )
Leidet unter dem gleichen Problem
std::isnan
, dh ist nicht zuverlässig.is_ieee754_nan( value ) )
Dies ist keine Standardfunktion. Es überprüft die Bits gemäß dem IEEE 754-Standard. Es ist absolut zuverlässig, aber der Code ist etwas systemabhängig.
Im folgenden vollständigen Testcode ist „Erfolg“, ob ein Ausdruck die Nan-Ness des Werts meldet. Für die meisten Ausdrücke entspricht dieses Erfolgsmaß, das Ziel, NaNs und nur NaNs zu erkennen, ihrer Standardsemantik. Für den
(value == Fp_info::quiet_NaN()) )
Ausdruck ist das Standardverhalten jedoch, dass er nicht als NaN-Detektor funktioniert.Ergebnisse mit g ++ (beachten Sie erneut, dass das Standardverhalten von
(value == Fp_info::quiet_NaN())
ist, dass es nicht als NaN-Detektor funktioniert, es ist hier nur sehr von praktischem Interesse):Ergebnisse mit Visual C ++:
Zusammenfassend lässt sich sagen, dass nur das direkte Testen der Darstellung auf Bitebene mit der
is_ieee754_nan
in diesem Testprogramm definierten Funktion in allen Fällen sowohl mit g ++ als auch mit Visual C ++ zuverlässig funktionierte.Nachtrag:
Nachdem ich das oben Gesagte veröffentlicht hatte, wurde mir ein weiterer möglicher Test für NaN bewusst, der in einer anderen Antwort hier erwähnt wurde, nämlich
((value < 0) == (value >= 0))
. Das hat mit Visual C ++ gut funktioniert, ist aber mit der-ffast-math
Option von g ++ fehlgeschlagen . Nur direkte Bitmusterprüfungen funktionieren zuverlässig.quelle
Dies funktioniert, wenn
sizeof(int)
4 undsizeof(long long)
8 ist.Während der Laufzeit ist es nur ein Vergleich, Gussteile brauchen keine Zeit. Es wird lediglich die Konfiguration der Vergleichsflags geändert, um die Gleichheit zu überprüfen.
quelle
memcpy
ein Byte-Array verwenden. Code dafür in meiner Antwort Nr. 2 .Eine mögliche Lösung, die nicht von der spezifischen IEEE-Darstellung für verwendetes NaN abhängt, wäre die folgende:
quelle
In Anbetracht dessen, dass (x! = X) für NaN nicht immer garantiert ist (z. B. wenn die Option -ffast-math verwendet wird), habe ich Folgendes verwendet:
Zahlen können nicht sowohl <0 als auch> = 0 sein, daher besteht diese Prüfung nur dann, wenn die Zahl weder kleiner noch größer oder gleich Null ist. Welches ist im Grunde überhaupt keine Zahl oder NaN.
Sie können dies auch verwenden, wenn Sie Folgendes bevorzugen:
Ich bin mir nicht sicher, wie sich dies auf -ffast-math auswirkt, daher kann Ihr Kilometerstand variieren.
quelle
f != f
es auch fehlerhaft ist. Ich habe gesehen, wie llvm einen fast identischen Code entfernt hat. Der Optimierer kann die Informationen über den ersten Vergleich weitergeben und herausfinden, dass der zweite Vergleich möglicherweise niemals wahr ist, wenn der erste Vergleich vorliegt. (Wenn der Compiler die IEEE-Regeln strikt einhält,f != f
ist das sowieso viel einfacher)-ffast-math
Option von g ++ . Funktioniert mit Visual C ++. Siehe ( stackoverflow.com/a/42138465/464581 ).Für mich könnte die Lösung ein Makro sein, um es explizit inline und damit schnell genug zu machen. Es funktioniert auch für jeden Float-Typ. Es basiert auf der Tatsache, dass der einzige Fall, in dem ein Wert nicht gleich ist, der ist, wenn der Wert keine Zahl ist.
quelle
Das funktioniert:
Ausgabe: isnan
quelle
Es scheint mir, dass der beste wirklich plattformübergreifende Ansatz darin besteht, eine Union zu verwenden und das Bitmuster des Double zu testen, um nach NaNs zu suchen.
Ich habe diese Lösung nicht gründlich getestet, und es gibt möglicherweise eine effizientere Möglichkeit, mit den Bitmustern zu arbeiten, aber ich denke, dass sie funktionieren sollte.
quelle
union
funktioniert diese Verwendung eines Wortspiels zwischen zwei Typen möglicherweise nicht wie gewünscht (: sad_panda :). Der richtige (wenn auch nicht ganz so portable wie gewünschte) Weg wäre, die Vereinigung insgesamt zu vermeiden und ein Memcpy von derdouble
in eine andereuint64_t
Variable durchzuführen und dann den Test mit dieser Hilfsvariablen durchzuführen.Unter x86-64 können Sie extrem schnelle Methoden zum Überprüfen auf NaN und unendlich verwenden, die unabhängig von der
-ffast-math
Compileroption funktionieren . (f != f
,std::isnan
,std::isinf
Ergeben immerfalse
mit-ffast-math
).Das Testen auf NaN, unendlich und endliche Zahlen kann leicht durchgeführt werden, indem nach dem maximalen Exponenten gesucht wird. unendlich ist maximaler Exponent mit Mantisse Null, NaN ist maximaler Exponent und Mantisse ungleich Null. Der Exponent wird in den nächsten Bits nach dem obersten Vorzeichenbit gespeichert, so dass wir die Verschiebung nach links verschieben können, um das Vorzeichenbit zu entfernen und den Exponenten zu den obersten Bits zu machen. Es ist keine Maskierung (
operator&
) erforderlich:Die
std
Versionen vonisinf
undisfinite
Laden von 2double/float
Konstanten aus dem.data
Segment und im schlimmsten Fall können sie 2 Datencache-Fehler verursachen. Die obigen Versionen laden keine Dateninf_double_shl1
undinf_float_shl1
Konstanten werden als unmittelbare Operanden in die Montageanweisungen codiert.Schneller
isnan2
sind nur 2 Montageanleitungen:Verwendet die Tatsache, dass der
ucomisd
Befehl das Paritätsflag setzt, wenn ein Argument NaN ist. Sostd::isnan
funktioniert es, wenn keine-ffast-math
Optionen angegeben sind.quelle
Der IEEE-Standard sagt, wenn der Exponent alle
1
s ist und die Mantisse nicht Null ist, ist die Zahl aNaN
. Double ist1
Vorzeichenbit,11
Exponentenbit und52
Mantissenbit. Mach ein bisschen nach.quelle
Wie oben erwähnt, funktioniert a! = A in g ++ und einigen anderen Compilern nicht, aber dieser Trick sollte. Es mag nicht so effizient sein, aber es ist immer noch ein Weg:
Grundsätzlich druckt printf in g ++ (bei anderen bin ich mir allerdings nicht sicher) 'nan' in den Formaten% d oder% .f, wenn die Variable keine gültige Ganzzahl / float ist. Daher prüft dieser Code, ob das erste Zeichen der Zeichenfolge 'n' ist (wie in "nan").
quelle
340282346638528859811704183484516925440.000
wenn a =FLT_MAX
. Er müsste verwendenchar s[7]; sprintf(s, "%.0g", a);
, das werden 6 chrs sein, wenna=-FLT_MAX
, oder-3e+38
Dies erkennt Unendlichkeit und auch NaN in Visual Studio, indem überprüft wird, ob es innerhalb doppelter Grenzen liegt:
quelle
FLT_MIN
,DBL_MIN
undLDBL_MIN
vorsichtiger. Dies sind die kleinsten normalisierten Werte für jeden Typ. Beispielsweise hat die einfache Genauigkeit über 8 Millionen legitime Denorm-Werte, die größer als Null und kleiner alsFLT_MIN
(und nicht NaN) sind.