¿Cómo usar nan e inf en C?

89

Tengo un método numérico que podría devolver nan o inf si hubiera un error, y para las pruebas me gustaría forzarlo temporalmente a devolver nan o inf para asegurar que la situación se maneje correctamente. ¿Existe una forma confiable e independiente del compilador de crear valores de nan e inf en C?

Después de buscar en Google durante unos 10 minutos, solo he podido encontrar soluciones dependientes del compilador.

Noob de gráficos
fuente
los flotadores no están definidos por el estándar C. Por lo tanto, no hay una forma independiente del compilador de hacer lo que quiere.
Johan Kotlinski

Respuestas:

86

Puede probar si su implementación lo tiene:

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

La existencia de INFINITYestá garantizada por C99 (o el último borrador al menos), y "se expande a una expresión constante de tipo float que representa infinito positivo o sin signo, si está disponible; de ​​lo contrario, a una constante positiva de tipo float que se desborda en el momento de la traducción".

NAN puede o no estar definido, y "se define si y solo si la implementación admite NaN silenciosos para el tipo float. Se expande a una expresión constante de tipo float que representa un NaN silencioso".

Tenga en cuenta que si está comparando valores de punto flotante y haga lo siguiente:

a = NAN;

incluso entonces,

a == NAN;

Es falso. Una forma de verificar el NaN sería:

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

También puede hacer: a != apara probar si aes NaN.

También hay isfinite(), isinf(), isnormal(), y signbit()macros en math.hen C99.

C99 también tiene nanfunciones:

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

(Referencia: n1256).

Docs INFINITY Docs NAN

Alok Singhal
fuente
2
Excelente respuesta. La referencia para las macros NAN e INFINITY es C99 §7.12 párrafos 4 y 5. Además (isnan (a)) también puede verificar NaN usando (a! = A) en una implementación conforme de C.
Stephen Canon
23
Por el amor de la lectura, a != adebe NUNCA ser utilizado.
Chris Kerekes
1
@ChrisKerekes: lamentablemente, algunos de nosotros tenemos NAN pero no isnan (). Sí, esto es 2017. :(
ef
C no requiere, cuando no aes un número, a == NANque devuelva falso. IEEE lo requiere. Incluso las implementaciones que se adhieren a IEEE, lo hacen en su mayoría . Cuando isnan()no está implementado, es mejor ajustar la prueba que codificar directamente a == NAN.
chux - Reincorporar a Monica
34

No existe una forma independiente del compilador de hacer esto, ya que ni los estándares C (ni los C ++) dicen que los tipos matemáticos de coma flotante deben admitir NAN o INF.

Editar: Acabo de verificar la redacción del estándar C ++, y dice que estas funciones (miembros de la clase de plantilla numeric_limits):

quiet_NaN() 
signalling_NaN()

devolverá las representaciones NAN "si están disponibles". No amplía lo que significa "si está disponible", sino presumiblemente algo como "si el representante de FP de la implementación los admite". Del mismo modo, hay una función:

infinity() 

que devuelve un representante INF positivo "si está disponible".

Ambos están definidos en el <limits>encabezado; supongo que el estándar C tiene algo similar (probablemente también "si está disponible"), pero no tengo una copia del estándar C99 actual.


fuente
Eso es decepcionante y sorprendente. ¿C y C ++ no se ajustan a los números de coma flotante IEEE, que tienen una representación estándar para nan e inf?
Graphics Noob
14
En C99, la cabecera de C <math.h>define nan(), nanf()y nanl()que devuelven diferentes representaciones de NaN (como double, float, y intrespectivamente), y el infinito (si está disponible) podría ser devuelto mediante la generación de una con log(0)o algo así. No hay una forma estándar de verificarlos, incluso en C99. El <float.h>encabezado ( <limits.h>es para tipos integrales) desafortunadamente no habla sobre los valores infy nan.
Chris Lutz
Vaya, eso es una gran confusión. nanl()devuelve un long double, no intcomo dice mi comentario. No sé por qué no me di cuenta de eso cuando lo estaba escribiendo.
Chris Lutz
@Chris, mira mi respuesta para C99.
Alok Singhal
2
@IngeHenriksen: estoy bastante seguro de que Microsoft ha declarado que no tiene la intención de que VC ++ sea compatible con C99.
Chris Lutz
24

Esto funciona para ambos floaty double:

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

Editar: Como ya dijo alguien, el antiguo estándar IEEE decía que tales valores deberían generar trampas. Pero los nuevos compiladores casi siempre desactivan las trampas y devuelven los valores dados porque la trampa interfiere con el manejo de errores.

Thorsten S.
fuente
La captura era una opción para el manejo de errores permitida bajo 754-1985. El comportamiento utilizado por la mayoría de los compiladores / hardware moderno también estaba permitido (y era el comportamiento preferido por muchos de los miembros del comité). Muchos implementadores asumieron incorrectamente que la captura era necesaria debido al desafortunado uso del término "excepciones" en la norma. Esto se ha aclarado en gran medida en el 754-2008 revisado.
Stephen Canon
Hola, Stephen, tienes razón, pero el estándar también dice: "Un usuario debería poder solicitar una captura en cualquiera de las cinco excepciones especificando un controlador para ella. Debería poder solicitar que se deshabilite un controlador existente , guardado o restaurado. También debería poder determinar si se ha habilitado un controlador de trampa específico para una excepción designada ". "debería" como se define (2. Definiciones) significa "muy recomendado" y su implementación sólo debe omitirse si la arquitectura, etc. lo hace impráctico. 80x86 es totalmente compatible con el estándar, por lo que no hay ninguna razón para que C no lo admita.
Thorsten S.
Estoy de acuerdo en que C debería exigir el punto flotante 754 (2008), pero hay buenas razones para no hacerlo; específicamente, C se usa en todo tipo de entornos que no sean x86, incluidos dispositivos integrados que no tienen hardware de punto flotante y dispositivos de procesamiento de señales donde los programadores ni siquiera quieren usar el punto flotante. Correcta o incorrectamente, esos usos explican una gran cantidad de inercia en la especificación del lenguaje.
Stephen Canon
No sé por qué la respuesta principal llegó allí. No da ninguna forma de producir los valores solicitados. Esta respuesta lo hace.
drysdam
#define is_nan(x) ((x) != (x))puede resultar útil como prueba sencilla y portátil para NAN.
Bob Stein
21

Una forma independiente del compilador, pero no una forma independiente del procesador para obtener estos:

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

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

Esto debería funcionar en cualquier procesador que utilice el formato de punto flotante IEEE 754 (que hace x86).

ACTUALIZACIÓN: Probado y actualizado.

Aaron
fuente
2
@WaffleMatt: ¿por qué este puerto no estaría entre 32/64 bits? El flotante de precisión simple IEEE 754 es de 32 bits, independientemente del tamaño de direccionamiento del procesador subyacente.
Aaron
6
¿Casting a (float &)? Eso no me parece C. Necesitasint i = 0x7F800000; return *(float *)&i;
Chris Lutz
6
Tenga en cuenta que 0x7f800001es un llamado NaN de señalización en el estándar IEEE-754. Aunque la mayoría de las bibliotecas y el hardware no admiten la señalización de NaN, es probable que sea mejor devolver un NaN silencioso como 0x7fc00000.
Stephen Canon
6
Advertencia: esto puede desencadenar un comportamiento indefinido a través de la violación de reglas estrictas de alias . La forma recomendada (y mejor respaldada por los compiladores) de hacer juegos de palabras con tipos es a través de miembros del sindicato .
ulidtko
2
Además del estricto problema de aliasing que señaló @ulidtko, esto supone que el objetivo usa el mismo endian para enteros como punto flotante, lo que definitivamente no siempre es el caso.
mr.stobbe
15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);
J. Kraftcheck
fuente
4
¡Esta es una solución portátil inteligente! C99 requiere strtody convierte NaN e Inf.
ulidtko
1
No es que haya un inconveniente con esta solución; no son constantes. No puede usar estos valores para inicializar una variable global, por ejemplo (o para inicializar una matriz).
Marc
1
@Bagazo. Siempre puede tener una función de inicialización que los llame una vez y los establezca en el espacio de nombres global. Es un inconveniente muy viable.
Mad Physicist
3
<inf.h>

/* IEEE positive infinity.  */

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

y

<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
fuente
0

También me sorprende que estas no sean constantes de tiempo de compilación. Pero supongo que podría crear estos valores con bastante facilidad simplemente ejecutando una instrucción que devuelva un resultado tan inválido. Dividiendo por 0, logaritmo de 0, bronceado de 90, ese tipo de cosas.

Carl Smotricz
fuente
0

Yo suelo usar

#define INFINITY (1e999)

o

const double INFINITY = 1e999

que funciona al menos en contextos IEEE 754 porque el valor doble representable más alto es aproximadamente 1e308. 1e309funcionaría igual de bien, 1e99999pero tres nueves son suficientes y memorables. Dado que este es un doble literal (en el #definecaso) o un Infvalor real , seguirá siendo infinito incluso si está utilizando flotantes de 128 bits ("doble largo").

Douglas Bagnall
fuente
1
Esto es muy peligroso, en mi opinión. Imagínese cómo alguien migra su código a flotadores de 128 bits en aproximadamente 20 años (después de que su código pasa por una evolución increíblemente compleja, ninguna de las etapas que pudo predecir hoy). De repente, el rango de exponentes aumenta drásticamente y todos sus 1e999literales ya no se redondean a +Infinity. Según las leyes de Murphy, esto rompe un algoritmo. Peor aún: un programador humano que realiza la compilación de "128 bits" probablemente no detectará ese error con anticipación. Es muy probable que sea demasiado tarde cuando se detecte y reconozca este error. Muy peligroso.
ulidtko
1
Por supuesto, el peor escenario anterior podría estar lejos de ser realista. ¡Pero aún así, considere las alternativas! Es mejor mantenerse en el lado seguro.
ulidtko
2
"En 20 años", je. Venga. Esta respuesta no es que malo.
alecov
@ulidtko A mí tampoco me gusta esto, pero ¿en serio?
Iharob Al Asimi
0

Aquí hay una forma simple de definir esas constantes, y estoy bastante seguro de que es portátil:

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

Cuando ejecuto este código:

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

Yo obtengo:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Patrick Chkoreff
fuente