Come usare nan e inf in C?


89

Ho un metodo numerico che potrebbe restituire nan o inf se ci fosse un errore, e per i test proposti vorrei forzarlo temporaneamente a restituire nan o inf per assicurarmi che la situazione sia gestita correttamente. Esiste un modo affidabile e indipendente dal compilatore per creare valori di nan e inf in C?

Dopo aver cercato su Google per circa 10 minuti, sono stato in grado di trovare solo soluzioni dipendenti dal compilatore.


i float non sono definiti dallo standard C. Quindi non esiste un modo indipendente dal compilatore per fare ciò che vuoi.
Johan Kotlinski

Risposte:


86

Puoi verificare se la tua implementazione lo ha:

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

L'esistenza di INFINITYè garantita da C99 (o almeno l'ultima bozza) e "si espande a un'espressione costante di tipo float che rappresenta un infinito positivo o senza segno, se disponibile; altrimenti a una costante positiva di tipo float che trabocca al momento della traduzione".

NAN può o non può essere definito, e "è definito se e solo se l'implementazione supporta NaN silenziosi per il tipo float. Si espande in un'espressione costante di tipo float che rappresenta un NaN silenzioso."

Nota che se stai confrontando valori in virgola mobile e fai:

a = NAN;

anche allora,

a == NAN;

è falso. Un modo per verificare NaN sarebbe:

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

Puoi anche fare: a != aper verificare se aè NaN.

C'è anche isfinite(), isinf(), isnormal(), e signbit()macro in math.hin C99.

C99 ha anche nanfunzioni:

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

(Riferimento: n1256).

Documenti INFINITY Documenti NAN


2
Ottima risposta. Il riferimento per le macro NAN e INFINITY è C99 §7.12 paragrafi 4 e 5. Inoltre (isnan (a)) puoi anche controllare NaN usando (a! = A) su un'implementazione conforme di C.
Stephen Canon

23
Per amore della leggibilità, nona != a dovrebbe MAI essere utilizzato.
Chris Kerekes

1
@ChrisKerekes: purtroppo alcuni di noi hanno NAN ma non isnan (). Sì, questo è il 2017. :(
eff

C non richiede, quando aè un non-numero, per a == NANrestituire falso. IEEE lo richiede. Anche le implementazioni che aderiscono a IEEE, lo fanno principalmente . Quando isnan()non è implementato, è comunque meglio eseguire il wrapping del test piuttosto che codice direttamente a == NAN.
chux - Ripristina Monica il

34

Non esiste un modo indipendente dal compilatore per eseguire questa operazione, poiché né gli standard C (né C ++) dicono che i tipi matematici in virgola mobile devono supportare NAN o INF.

Modifica: ho appena controllato la dicitura dello standard C ++ e dice che queste funzioni (membri della classe numeric_limits basata su modelli):

quiet_NaN() 
signalling_NaN()

restituirà le rappresentazioni NAN "se disponibili". Non si espande su cosa significhi "se disponibile", ma presumibilmente qualcosa come "se il rappresentante FP dell'implementazione li supporta". Allo stesso modo, c'è una funzione:

infinity() 

che restituisce un rappresentante INF positivo "se disponibile".

Entrambi sono definiti <limits>nell'intestazione: immagino che lo standard C abbia qualcosa di simile (probabilmente anche "se disponibile") ma non ho una copia dell'attuale standard C99.


È deludente e sorprendente. C e C ++ non sono conformi ai numeri in virgola mobile IEEE, che hanno una rappresentazione standard per nan e inf?
Grafica Noob

14
In C99, l'intestazione C <math.h>definisce nan(), nanf()e nanl()che restituiscono diverse rappresentazioni di NaN (come double, floate intrispettivamente), e infinito (se disponibile) potrebbe essere restituito generando uno con log(0)o qualcosa. Non esiste un modo standard per verificarli, anche in C99. L' <float.h>intestazione ( <limits.h>è per i tipi integrali) è purtroppo silenziosa su infe nanvalori.
Chris Lutz

Wow, è un grosso miscuglio. nanl()restituisce un long double, non un intcome dice il mio commento. Non so perché non me ne sono reso conto mentre lo scrivevo.
Chris Lutz

@ Chris, vedi la mia risposta per C99.
Alok Singhal,

2
@IngeHenriksen - Abbastanza sicuro che Microsoft abbia dichiarato di non avere intenzione che VC ++ supporti C99.
Chris Lutz

24

Funziona per entrambi floate double:

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

Modifica: come qualcuno ha già detto, il vecchio standard IEEE diceva che tali valori dovrebbero sollevare trappole. Ma i nuovi compilatori disattivano quasi sempre i trap e restituiscono i valori dati perché il trapping interferisce con la gestione degli errori.


Il trapping era un'opzione per la gestione degli errori consentita in 754-1985. Era consentito anche il comportamento usato dalla maggior parte dei moderni hardware / compilatori (ed era il comportamento preferito da molti dei membri del comitato). Molti implementatori presumevano erroneamente che il trapping fosse necessario a causa dello sfortunato utilizzo del termine "eccezioni" nello standard. Ciò è stato ampiamente chiarito nella revisione 754-2008.
Stephen Canon

Ciao, Stephen, hai ragione, ma lo standard dice anche: "Un utente dovrebbe essere in grado di richiedere un trap su una qualsiasi delle cinque eccezioni specificando un gestore per esso. Dovrebbe essere in grado di richiedere che un gestore esistente sia disabilitato , salvato o ripristinato. Dovrebbe anche essere in grado di determinare se è stato abilitato un gestore trap specifico per un'eccezione designata. " "dovrebbe" come definito (2. Definizioni) significa "fortemente raccomandato" e la sua implementazione dovrebbe essere tralasciata solo se l'architettura ecc. la rende poco pratica. 80x86 supporta completamente lo standard, quindi non c'è motivo per cui C non lo supporti.
Thorsten S.

Sono d'accordo che C dovrebbe richiedere 754 (2008) in virgola mobile, ma ci sono buone ragioni per non farlo; in particolare, C viene utilizzato in tutti i tipi di ambienti diversi da x86, inclusi i dispositivi embedded che non dispongono di virgola mobile hardware e dispositivi di elaborazione del segnale in cui i programmatori non vogliono nemmeno utilizzare il virgola mobile. Giustamente o erroneamente, questi usi spiegano molta inerzia nelle specifiche del linguaggio.
Stephen Canon,

Non so perché la risposta migliore sia arrivata lì. Non fornisce alcun modo per produrre i valori richiesti. Questa risposta sì.
drysdam

#define is_nan(x) ((x) != (x))può essere utile come semplice test portatile per NAN.
Bob Stein,

21

Un modo indipendente dal compilatore, ma non un modo indipendente dal processore per ottenere questi:

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

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

Questo dovrebbe funzionare su qualsiasi processore che utilizza il formato a virgola mobile IEEE 754 (che fa x86).

AGGIORNAMENTO: testato e aggiornato.


2
@ WaffleMatt - perché questa porta non dovrebbe essere compresa tra 32/64 bit? Il float a precisione singola IEEE 754 è a 32 bit indipendentemente dalle dimensioni di indirizzamento del processore sottostante.
Aaron

6
Trasmetti a (float &)? A me non sembra C. Hai bisogno diint i = 0x7F800000; return *(float *)&i;
Chris Lutz

6
Si noti che 0x7f800001è un cosiddetto NaN di segnalazione nello standard IEEE-754. Sebbene la maggior parte delle librerie e dell'hardware non supportino i NaN di segnalazione, è probabilmente meglio restituire un NaN silenzioso come 0x7fc00000.
Stephen Canon

6
Attenzione: questo può attivare un comportamento indefinito attraverso la violazione di rigide regole di aliasing . Il modo consigliato (e meglio supportato nei compilatori) per eseguire il gioco di parole è tramite i membri del sindacato .
ulidtko

2
Oltre allo stretto problema di aliasing evidenziato da @ulidtko, si presume che l'obiettivo utilizzi lo stesso endian per gli interi come virgola mobile, il che sicuramente non è sempre il caso.
mr.stobbe

15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);

4
Questa è una soluzione portatile intelligente! C99 richiede strtode converte NaN e Inf.
ulidtko

1
Non che ci sia un inconveniente con questa soluzione; non sono costanti. Non è possibile utilizzare questi valori per inizializzare ad esempio una variabile globale (o per inizializzare un array).
Marc

1
@ Marc. È sempre possibile avere una funzione di inizializzazione che li chiama una volta e li imposta nello spazio dei nomi globale. È un inconveniente molto praticabile.
Mad Physicist

3
<inf.h>

/* IEEE positive infinity.  */

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

e

<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.  */

0

Sono anche sorpreso che queste non siano costanti del tempo di compilazione. Ma suppongo che potresti creare questi valori abbastanza facilmente semplicemente eseguendo un'istruzione che restituisce un risultato così non valido. Dividendo per 0, log di 0, tan di 90, quel genere di cose.


0

Di solito uso

#define INFINITY (1e999)

o

const double INFINITY = 1e999

che funziona almeno in contesti IEEE 754 perché il valore double più alto rappresentabile è approssimativamente 1e308. 1e309funzionerebbe altrettanto bene, come sarebbe 1e99999, ma tre nove sono sufficienti e memorabili. Poiché questo è un valore letterale doppio (nel #definecaso) o effettivo Inf, rimarrà infinito anche se stai utilizzando float a 128 bit ("doppio lungo").


1
Questo è molto pericoloso, secondo me. Immagina come qualcuno migra il tuo codice in float a 128 bit in circa 20 anni (dopo che il tuo codice ha attraversato un'evoluzione incredibilmente complessa, nessuna delle fasi di cui sei stato in grado di prevedere oggi). All'improvviso, l'intervallo degli esponenti aumenta drasticamente e tutti i tuoi 1e999letterali non si arrotondano più a +Infinity. Secondo le leggi di Murphy, questo rompe un algoritmo. Peggio: un programmatore umano che esegue la build a "128 bit" probabilmente non individuerà in anticipo quell'errore. Cioè molto probabilmente sarà troppo tardi quando questo errore verrà trovato e riconosciuto. Molto pericoloso.
ulidtko

1
Ovviamente, lo scenario peggiore di cui sopra potrebbe essere tutt'altro che realistico. Tuttavia, considera le alternative! È meglio stare dalla parte della sicurezza.
ulidtko

2
"Tra 20 anni", eh. Dai. Questa risposta non è poi così male.
alecov

@ulidtko neanche a me piace, ma davvero?
Iharob Al Asimi

0

Ecco un modo semplice per definire quelle costanti e sono abbastanza sicuro che sia portatile:

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

Quando eseguo questo codice:

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

Ottengo:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.