Wie benutzt man nan und inf in C?

87

Ich habe eine numerische Methode, die nan oder inf zurückgeben kann, wenn ein Fehler aufgetreten ist, und zu Testzwecken möchte ich sie vorübergehend zwingen, nan oder inf zurückzugeben, um sicherzustellen, dass die Situation korrekt behandelt wird. Gibt es eine zuverlässige, compilerunabhängige Möglichkeit, Werte für nan und inf in C zu erstellen?

Nachdem ich ungefähr 10 Minuten gegoogelt habe, konnte ich nur compilerabhängige Lösungen finden.

Grafik Noob
quelle
Schwimmer sind nicht durch den C-Standard definiert. Es gibt also keine compilerunabhängige Möglichkeit, das zu tun, was Sie wollen.
Johan Kotlinski

Antworten:

83

Sie können testen, ob Ihre Implementierung es hat:

#include <math.h>
#ifdef NAN
/* NAN is supported */
#endif
#ifdef INFINITY
/* INFINITY is supported */
#endif

Die Existenz von INFINITYwird durch C99 (oder zumindest den neuesten Entwurf) garantiert und "erweitert sich zu einem konstanten Ausdruck des Typs float, der eine positive oder vorzeichenlose Unendlichkeit darstellt, falls verfügbar; andernfalls zu einer positiven Konstante des Typs float, die zur Übersetzungszeit überläuft."

NAN kann definiert werden oder nicht, und "wird genau dann definiert, wenn die Implementierung leise NaNs für den Float-Typ unterstützt. Sie wird zu einem konstanten Ausdruck des Typs float erweitert, der ein leises NaN darstellt."

Beachten Sie Folgendes, wenn Sie Gleitkommawerte vergleichen, und gehen Sie wie folgt vor:

a = NAN;

sogar dann,

a == NAN;

ist falsch. Eine Möglichkeit, nach NaN zu suchen, wäre:

#include <math.h>
if (isnan(a)) { ... }

Sie können auch Folgendes tun: um a != azu testen, ob aNaN vorhanden ist.

Es gibt auch isfinite(), isinf(), isnormal(), und signbit()Makros in math.hin C99.

C99 hat auch nanFunktionen:

#include <math.h>
double nan(const char *tagp);
float nanf(const char *tagp);
long double nanl(const char *tagp);

(Referenz: n1256).

Docs INFINITY Docs NAN

Alok Singhal
quelle
2
Hervorragende Antwort. Die Referenz für die Makros NAN und INFINITY ist C99 §7.12 Absätze 4 und 5. Neben (isnan (a)) können Sie auch mit (a! = A) auf NaN prüfen, ob eine konforme Implementierung von C.
Stephen Canon
23
Aus Liebe zur Lesbarkeit a != asollte NIEMALS verwendet werden.
Chris Kerekes
1
@ ChrisKerekes: Leider haben einige von uns NAN, aber nicht isnan (). Ja, das ist 2017 .:(
eff
C benötigt nicht, wenn aes sich um eine Nicht-Zahl handelt, um a == NANfalse zurückzugeben. IEEE erfordert es. Sogar Implementierungen, die IEEE entsprechen, tun dies meistens . Wenn isnan()nicht implementiert, ist es immer noch besser, den Test zu verpacken als direkt zu codieren a == NAN.
chux - Monica
34

Es gibt keine vom Compiler unabhängige Methode, da weder die C- (noch die C ++) Standards besagen, dass die Gleitkomma-Mathe-Typen NAN oder INF unterstützen müssen.

Bearbeiten: Ich habe gerade den Wortlaut des C ++ - Standards überprüft und es heißt, dass diese Funktionen (Mitglieder der Vorlagenklasse numeric_limits):

quiet_NaN() 
signalling_NaN()

Wir werden NAN-Darstellungen "falls verfügbar" zurückgeben. Es wird nicht erweitert, was "falls verfügbar" bedeutet, sondern vermutlich "wenn der FP-Vertreter der Implementierung sie unterstützt". Ebenso gibt es eine Funktion:

infinity() 

Dies gibt einen positiven INF-Repräsentanten "falls verfügbar" zurück.

Diese sind beide in der <limits>Kopfzeile definiert - ich würde vermuten, dass der C-Standard etwas Ähnliches hat (wahrscheinlich auch "falls verfügbar"), aber ich habe keine Kopie des aktuellen C99-Standards.


quelle
Das ist enttäuschend und überraschend. Stimmen C und C ++ nicht mit den IEEE-Gleitkommazahlen überein, die eine Standarddarstellung für nan und inf haben?
Grafik Noob
14
In C99, die C - Header <math.h>definiert nan(), nanf()und nanl()dass die Rückkehr verschiedener Darstellungen von NaN (als a double, floatund intjeweils) und unendlich (wenn vorhanden) durch die Erzeugung eines mit zurückgeführt werden könnte log(0)oder so. Selbst in C99 gibt es keine Standardmethode, um nach ihnen zu suchen. Der <float.h>Header ( <limits.h>ist für integrale Typen) enthält leider keine Informationen infund nanWerte.
Chris Lutz
Wow, das ist eine große Verwechslung. nanl()gibt ein zurück long double, nicht intwie mein Kommentar sagt. Ich weiß nicht, warum ich das nicht bemerkt habe, als ich es getippt habe.
Chris Lutz
@ Chris, siehe meine Antwort für C99.
Alok Singhal
2
@IngeHenriksen - Ziemlich sicher, dass Microsoft angegeben hat, dass VC ++ nicht die Absicht hat, C99 zu unterstützen.
Chris Lutz
23

Dies funktioniert für beide floatund double:

double NAN = 0.0/0.0;
double POS_INF = 1.0 /0.0;
double NEG_INF = -1.0/0.0;

Bearbeiten: Wie jemand bereits sagte, sagte der alte IEEE-Standard, dass solche Werte Fallen auslösen sollten. Die neuen Compiler schalten jedoch fast immer die Traps aus und geben die angegebenen Werte zurück, da das Trapping die Fehlerbehandlung beeinträchtigt.

Thorsten S.
quelle
Das Überfüllen war eine Option für die Fehlerbehandlung, die unter 754-1985 zulässig war. Das von den meisten modernen Hardware- / Compilern verwendete Verhalten war ebenfalls zulässig (und war für viele Mitglieder des Ausschusses das bevorzugte Verhalten). Viele Implementierer gingen fälschlicherweise davon aus, dass aufgrund der unglücklichen Verwendung des Begriffs "Ausnahmen" im Standard ein Überfüllen erforderlich ist. Dies wurde in der überarbeiteten Fassung 754-2008 klargestellt.
Stephen Canon
Hallo Stephen, Sie haben Recht, aber der Standard sagt auch: "Ein Benutzer sollte in der Lage sein, eine Falle für eine der fünf Ausnahmen anzufordern, indem er einen Handler dafür angibt. Er sollte in der Lage sein, die Deaktivierung eines vorhandenen Handlers anzufordern , sollte gespeichert oder wiederhergestellt werden. Er sollte auch feststellen können, ob ein bestimmter Trap-Handler für eine bestimmte Ausnahme aktiviert wurde. " "sollte" wie definiert (2. Definitionen) bedeutet "dringend empfohlen" und seine Implementierung sollte nur weggelassen werden, wenn die Architektur usw. dies unpraktisch macht. 80x86 unterstützt den Standard vollständig, daher gibt es keinen Grund für C, ihn nicht zu unterstützen.
Thorsten S.
Ich bin damit einverstanden, dass C einen Gleitkommawert von 754 (2008) erfordern sollte, aber es gibt gute Gründe dafür, dies nicht zu tun. Insbesondere wird C in allen anderen Umgebungen als x86 verwendet - einschließlich eingebetteter Geräte ohne Hardware-Gleitkomma und Signalverarbeitungsgeräte, in denen Programmierer nicht einmal Gleitkomma verwenden möchten. Zu Recht oder zu Unrecht verursachen diese Verwendungen eine große Trägheit in der Sprachspezifikation.
Stephen Canon
Ich weiß nicht, warum die Top-Antwort es dort geschafft hat. Es gibt keine Möglichkeit, die angeforderten Werte zu erzeugen. Diese Antwort tut es.
Drysdam
#define is_nan(x) ((x) != (x))kann als einfacher, tragbarer Test für NAN nützlich sein.
Bob Stein
21

Ein compilerunabhängiger Weg, aber kein prozessorunabhängiger Weg, um diese zu erhalten:

int inf = 0x7F800000;
return *(float*)&inf;

int nan = 0x7F800001;
return *(float*)&nan;

Dies sollte auf jedem Prozessor funktionieren, der das Gleitkommaformat IEEE 754 verwendet (was x86 tut).

UPDATE: Getestet und aktualisiert.

Aaron
quelle
2
@WaffleMatt - warum sollte dieser Port nicht zwischen 32/64 Bit liegen? Der Float mit einfacher Genauigkeit nach IEEE 754 ist 32-Bit, unabhängig von der Adressierungsgröße des zugrunde liegenden Prozessors.
Aaron
6
Casting zu (float &)? Das sieht für mich nicht nach C aus. Sie brauchenint i = 0x7F800000; return *(float *)&i;
Chris Lutz
6
Beachten Sie, dass 0x7f800001dies im IEEE-754-Standard ein sogenanntes Signalisierungs- NaN ist. Obwohl die meisten Bibliotheken und Hardware keine Signalisierung von NaNs unterstützen, ist es wahrscheinlich besser, ein leises NaN wie zurückzugeben 0x7fc00000.
Stephen Canon
6
Warnung: Dies kann undefiniertes Verhalten durch Verletzung strenger Aliasing- Regeln auslösen . Die empfohlene (und am besten von Compilern unterstützte) Methode, Typ-Punning durchzuführen, sind Gewerkschaftsmitglieder .
Ulidtko
2
Zusätzlich zu dem strengen Aliasing-Problem, auf das @ulidtko hingewiesen hat, wird davon ausgegangen, dass das Ziel für Ganzzahlen denselben Endian wie Gleitkomma verwendet, was definitiv nicht immer der Fall ist.
mr.stobbe
14
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);
J. Kraftcheck
quelle
3
Dies ist eine intelligente tragbare Lösung! C99 benötigt strtodund konvertiert NaNs und Infs.
Ulidtko
1
Nicht, dass diese Lösung einen Nachteil hat. Sie sind keine Konstanten. Sie können diese Werte beispielsweise nicht zum Initialisieren einer globalen Variablen (oder zum Initialisieren eines Arrays) verwenden.
Marc
1
@ Marc. Sie können jederzeit über eine Initialisierungsfunktion verfügen, die diese einmal aufruft und im globalen Namespace festlegt. Es ist ein sehr praktikabler Nachteil.
Mad Physicist
3
<inf.h>

/* IEEE positive infinity.  */

#if __GNUC_PREREQ(3,3)
# define INFINITY   (__builtin_inff())
#else
# define INFINITY   HUGE_VALF
#endif

und

<bits/nan.h>
#ifndef _MATH_H
# error "Never use <bits/nan.h> directly; include <math.h> instead."
#endif


/* IEEE Not A Number.  */

#if __GNUC_PREREQ(3,3)

# define NAN    (__builtin_nanf (""))

#elif defined __GNUC__

# define NAN \
  (__extension__                                  \
   ((union { unsigned __l __attribute__ ((__mode__ (__SI__))); float __d; })  \
    { __l: 0x7fc00000UL }).__d)

#else

# include <endian.h>

# if __BYTE_ORDER == __BIG_ENDIAN
#  define __nan_bytes       { 0x7f, 0xc0, 0, 0 }
# endif
# if __BYTE_ORDER == __LITTLE_ENDIAN
#  define __nan_bytes       { 0, 0, 0xc0, 0x7f }
# endif

static union { unsigned char __c[4]; float __d; } __nan_union
    __attribute_used__ = { __nan_bytes };
# define NAN    (__nan_union.__d)

#endif  /* GCC.  */
4pie0
quelle
0

Ich bin auch überrascht, dass dies keine Kompilierungszeitkonstanten sind. Aber ich nehme an, Sie könnten diese Werte leicht genug erstellen, indem Sie einfach eine Anweisung ausführen, die ein solches ungültiges Ergebnis zurückgibt. Teilen durch 0, log von 0, tan von 90, so etwas.

Carl Smotricz
quelle
0

Ich benutze normalerweise

#define INFINITY (1e999)

oder

const double INFINITY = 1e999

Dies funktioniert zumindest in IEEE 754-Kontexten, da der höchste darstellbare Doppelwert ungefähr ist 1e308. 1e309würde genauso gut funktionieren wie würde 1e99999, aber drei Neunen sind ausreichend und unvergesslich. Da dies entweder ein Doppelliteral (im #defineFall) oder ein tatsächlicher InfWert ist, bleibt es unendlich, selbst wenn Sie 128-Bit-Floats („Long Double“) verwenden.

Douglas Bagnall
quelle
1
Das ist meiner Meinung nach sehr gefährlich. Stellen Sie sich vor, wie jemand Ihren Code in etwa 20 Jahren auf 128-Bit-Floats migriert (nachdem Ihr Code eine unglaublich komplexe Entwicklung durchlaufen hat, von der keine der Phasen, die Sie heute vorhersagen konnten). Plötzlich erhöht sich der Exponentenbereich drastisch und alle Ihre 1e999Literale runden nicht mehr auf +Infinity. Nach Murphys Gesetzen bricht dies einen Algorithmus. Schlimmer noch: Ein menschlicher Programmierer, der den "128-Bit" -Bau ausführt, wird diesen Fehler wahrscheinlich nicht im Voraus erkennen. Das heißt, es wird höchstwahrscheinlich zu spät sein, wenn dieser Fehler gefunden und erkannt wird. Sehr gefährlich.
Ulidtko
1
Natürlich könnte das oben beschriebene Worst-Case-Szenario alles andere als realistisch sein. Aber bedenken Sie trotzdem die Alternativen! Es ist besser, auf der sicheren Seite zu bleiben.
Ulidtko
2
"In 20 Jahren", heh. Komm schon. Diese Antwort ist nicht so schlecht.
Alecov
@ulidtko Das gefällt mir auch nicht, aber wirklich?
Iharob Al Asimi
0

Hier ist eine einfache Möglichkeit, diese Konstanten zu definieren, und ich bin mir ziemlich sicher, dass sie portabel sind:

const double inf = 1.0/0.0;
const double nan = 0.0/0.0;

Wenn ich diesen Code ausführe:

printf("inf  = %f\n", inf);
printf("-inf = %f\n", -inf);
printf("nan  = %f\n", nan);
printf("-nan = %f\n", -nan);

Ich bekomme:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Patrick Chkoreff
quelle