Jak używać nan i inf w C?

89

Mam metodę numeryczną, która mogłaby zwrócić nan lub inf, jeśli wystąpił błąd, i dla celów testowania chciałbym tymczasowo zmusić ją do zwrócenia nan lub inf, aby upewnić się, że sytuacja jest obsługiwana poprawnie. Czy istnieje niezawodny, niezależny od kompilatora sposób tworzenia wartości nan i inf w języku C?

Po około 10 minutach wyszukiwania w Google udało mi się znaleźć tylko rozwiązania zależne od kompilatora.

Grafika Noob
źródło
pływaki nie są zdefiniowane w standardzie C. Nie ma więc niezależnego od kompilatora sposobu robienia tego, co chcesz.
Johan Kotlinski

Odpowiedzi:

86

Możesz sprawdzić, czy Twoja implementacja to ma:

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

Istnienie INFINITYjest gwarantowane przez C99 (lub przynajmniej najnowszą wersję roboczą) i „rozwija się do stałego wyrażenia typu float reprezentującego dodatnią lub bez znaku nieskończoność, jeśli jest dostępna; w przeciwnym razie do dodatniej stałej typu float, która przepełnia się w czasie tłumaczenia”.

NAN może być zdefiniowane lub nie, oraz „jest zdefiniowane wtedy i tylko wtedy, gdy implementacja obsługuje ciche NaN dla typu zmiennoprzecinkowego. Rozwija się do stałego wyrażenia typu float reprezentującego ciche NaN”.

Zauważ, że jeśli porównujesz wartości zmiennoprzecinkowe, zrób:

a = NAN;

nawet wtedy,

a == NAN;

to fałsz. Jednym ze sposobów sprawdzenia NaN jest:

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

Możesz także: a != asprawdzić, czy ajest NaN.

Jest też isfinite(), isinf(), isnormal()i signbit()makr w math.hw C99.

C99 ma również nanfunkcje:

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

(Odniesienie: n1256).

Dokumenty INFINITY Dokumenty NAN

Alok Singhal
źródło
2
Doskonała odpowiedź. Odniesieniem do makr NAN i INFINITY jest C99 §7.12 paragrafy 4 i 5. Poza tym (isnan (a)) możesz również sprawdzić NaN używając (a! = A) na zgodnej implementacji C.
Stephen Canon
23
Na miłość czytelności, a != apowinna nigdy być używane.
Chris Kerekes,
1
@ChrisKerekes: niestety, niektórzy z nas mają NAN, ale nie isnan (). Tak, to jest 2017 :(
eff
C nie wymaga, aby, gdy ajest nie-liczbą, a == NANzwracało fałsz. IEEE tego wymaga. Nawet implementacje, które są zgodne z IEEE, robią to głównie . Gdy isnan()nie jest zaimplementowany, nadal lepiej jest opakować test niż bezpośrednio kodować a == NAN.
chux - Przywróć Monikę
34

Nie ma na to niezależnego od kompilatora sposobu, ponieważ ani standardy C (ani C ++) nie mówią, że typy matematyczne zmiennoprzecinkowe muszą obsługiwać NAN lub INF.

Edycja: Właśnie sprawdziłem brzmienie standardu C ++ i mówi, że te funkcje (członkowie klasy numeric_limits):

quiet_NaN() 
signalling_NaN()

zwróci reprezentacje NAN „jeśli są dostępne”. Nie rozwija tego, co oznacza „jeśli jest dostępny”, ale prawdopodobnie coś w stylu „jeśli przedstawiciel programu ramowego implementacji je obsługuje”. Podobnie jest funkcja:

infinity() 

co zwraca dodatni wynik INF „jeśli jest dostępny”.

Oba są zdefiniowane w <limits>nagłówku - domyślam się, że standard C ma coś podobnego (prawdopodobnie też "jeśli jest dostępny"), ale nie mam kopii obecnego standardu C99.


źródło
To rozczarowujące i zaskakujące. Czy C i C ++ nie są zgodne z liczbami zmiennoprzecinkowymi IEEE, które mają standardową reprezentację dla nan i inf?
Grafika Noob
14
W C99, ç nagłówka <math.h>Ustala nan(), nanf()i nanl()że obie różnymi reprezentacjami NaN (w double, floati int, odpowiednio), a nieskończonością (jeśli jest dostępna) mogłaby być zwracana przez generowanie jednego z log(0)albo coś. Nie ma standardowego sposobu ich sprawdzenia, nawet w C99. <float.h>Header ( <limits.h>dla integralnych typów) jest niestety milczy na ten temat infi nanwartości.
Chris Lutz,
Wow, to wielka pomyłka. nanl()zwraca a long double, a nie intjak mówi mój komentarz. Nie wiem, dlaczego nie zdawałem sobie z tego sprawy, kiedy to pisałem.
Chris Lutz,
@Chris, zobacz moją odpowiedź dla C99.
Alok Singhal
2
@IngeHenriksen - Jestem prawie pewien, że Microsoft oświadczył, że nie ma zamiaru VC ++ obsługiwać C99.
Chris Lutz
24

Działa to zarówno w przypadku, jak floati double:

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

Edycja: Jak ktoś już powiedział, stary standard IEEE mówił, że takie wartości powinny powodować pułapki. Jednak nowe kompilatory prawie zawsze wyłączają pułapki i zwracają podane wartości, ponieważ pułapki przeszkadzają w obsłudze błędów.

Thorsten S.
źródło
Zalewkowanie było jedną z opcji obsługi błędów dozwoloną w 754-1985. Zachowanie używane przez większość współczesnego sprzętu / kompilatorów było również dozwolone (i było preferowanym zachowaniem dla wielu członków komitetu). Wielu wdrażających błędnie założyło, że pułapki są wymagane z powodu niefortunnego użycia terminu „wyjątki” w standardzie. Zostało to znacznie wyjaśnione w poprawionym dokumencie 754-2008.
Stephen Canon
Cześć, Stephen, masz rację, ale norma mówi również: „Użytkownik powinien mieć możliwość zażądania pułapki na dowolny z pięciu wyjątków, określając dla niej procedurę obsługi. Powinien mieć możliwość zażądania wyłączenia istniejącej procedury obsługi , zapisany lub przywrócony. Powinien także być w stanie określić, czy została włączona określona procedura obsługi pułapki dla wyznaczonego wyjątku ”. „powinien” zgodnie z definicją (2. Definicje) oznacza „zdecydowanie zalecane”, a jego implementację należy pominąć tylko wtedy, gdy architektura itp. sprawia, że ​​jest to niepraktyczne. 80x86 w pełni obsługuje standard, więc nie ma powodu, aby C go nie obsługiwał.
Thorsten S.
Zgadzam się, że C powinien wymagać zmiennoprzecinkowego 754 (2008), ale są dobre powody, aby tego nie robić; w szczególności język C jest używany we wszystkich rodzajach środowisk innych niż x86 - w tym w urządzeniach wbudowanych, które nie mają sprzętowych wartości zmiennoprzecinkowych, oraz w urządzeniach do przetwarzania sygnałów, w których programiści nie chcą nawet używać liczb zmiennoprzecinkowych. Słusznie lub niesłusznie, te zastosowania powodują dużą inercję w specyfikacji języka.
Stephen Canon
Nie wiem, dlaczego znalazła się tam najlepsza odpowiedź. Nie daje żadnego sposobu na wygenerowanie żądanych wartości. Ta odpowiedź tak.
drysdam
#define is_nan(x) ((x) != (x))może być przydatny jako prosty, przenośny test sieci NAN.
Bob Stein
21

Sposób niezależny od kompilatora, ale nie niezależny od procesora sposób na uzyskanie tych:

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

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

Powinno to działać na każdym procesorze, który używa formatu zmiennoprzecinkowego IEEE 754 (co robi x86).

UPDATE: przetestowane i zaktualizowane.

Aaron
źródło
2
@WaffleMatt - dlaczego nie ten port między 32/64 bitami? Liczba zmiennoprzecinkowa pojedynczej precyzji IEEE 754 jest 32-bitowa, niezależnie od rozmiaru adresowania bazowego procesora.
Aaron
6
Przesyłasz do (float &)? To nie wygląda mi na C. Potrzebujeszint i = 0x7F800000; return *(float *)&i;
Chris Lutz,
6
Zauważ, że 0x7f800001jest to tak zwany sygnalizacyjny NaN w standardzie IEEE-754. Chociaż większość bibliotek i sprzętu nie obsługuje sygnalizacji NaN, prawdopodobnie lepiej jest zwrócić cichy typ NaN 0x7fc00000.
Stephen Canon
6
Ostrzeżenie: może to wywołać niezdefiniowane zachowanie poprzez naruszenie ścisłych zasad aliasingu . Zalecanym (i najlepiej obsługiwanym w kompilatorach) sposobem wykonywania punningu jest użycie członków unii .
ulidtko
2
Oprócz ścisłego problemu aliasingu, na który zwrócił uwagę @ulidtko, zakłada się, że cel używa tego samego endianu dla liczb całkowitych co zmiennoprzecinkowy, co zdecydowanie nie zawsze ma miejsce.
mr.stobbe
15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);
J.Kraftcheck
źródło
4
To sprytne, przenośne rozwiązanie! C99 wymaga strtodi konwertuje NaN i Inf.
ulidtko
1
Nie oznacza to, że to rozwiązanie ma wadę; nie są stałymi. Nie można użyć tych wartości do zainicjowania zmiennej globalnej na przykład (lub do zainicjowania tablicy).
Marc
1
@Marc. Zawsze możesz mieć funkcję inicjalizującą, która wywołuje je raz i ustawia je w globalnej przestrzeni nazw. To bardzo praktyczna wada.
Mad Physicist
3
<inf.h>

/* IEEE positive infinity.  */

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

i

<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
źródło
0

Jestem również zaskoczony, że to nie są stałe czasu kompilacji. Ale przypuszczam, że możesz łatwo utworzyć te wartości, wykonując po prostu instrukcję, która zwraca taki nieprawidłowy wynik. Dzielenie przez 0, log z 0, tan z 90, coś w tym rodzaju.

Carl Smotricz
źródło
0

Zwykle używam

#define INFINITY (1e999)

lub

const double INFINITY = 1e999

który działa przynajmniej w kontekstach IEEE 754, ponieważ najwyższa reprezentowalna wartość podwójna jest z grubsza 1e308. 1e309działałyby równie dobrze, jak byłyby 1e99999, ale trzy dziewiątki są wystarczające i niezapomniane. Ponieważ jest to podwójny literał (w tym #defineprzypadku) lub rzeczywista Infwartość, pozostanie nieskończona, nawet jeśli używasz 128-bitowych („długie podwójne”) wartości zmiennoprzecinkowe.

Douglas Bagnall
źródło
1
Moim zdaniem jest to bardzo niebezpieczne. Wyobraź sobie, jak ktoś przeprowadza migrację Twojego kodu do 128-bitowych zmiennych w ciągu około 20 lat (po tym, jak Twój kod przejdzie niewiarygodnie złożoną ewolucję, której żaden z etapów nie byłby w stanie dziś przewidzieć). Nagle zakres wykładnika drastycznie się zwiększa, a wszystkie twoje 1e999literały nie są już zaokrąglane do +Infinity. Według praw Murphy'ego, to łamie algorytm. Gorzej: programista wykonujący „128-bitową” kompilację prawdopodobnie nie zauważy wcześniej tego błędu. Tzn. Najprawdopodobniej będzie za późno, gdy ten błąd zostanie znaleziony i rozpoznany. Bardzo niebezpieczne.
ulidtko
1
Oczywiście powyższy najgorszy scenariusz może być daleki od realistycznego. Ale nadal rozważ alternatywy! Lepiej pozostać po bezpiecznej stronie.
ulidtko
2
„Za 20 lat”, heh. Daj spokój. Ta odpowiedź nie jest taka zła.
alecov
@ulidtko Też mi się to nie podoba, ale naprawdę?
Iharob Al Asimi
0

Oto prosty sposób zdefiniowania tych stałych i jestem prawie pewien, że jest przenośny:

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

Kiedy uruchamiam ten kod:

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

Dostaję:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Patrick Chkoreff
źródło