Comprobando si un doble (o flotante) es NaN en C ++

369

¿Hay una función isnan ()?

PD .: Estoy en MinGW (si eso hace la diferencia).

Esto se resolvió mediante el uso de isnan () de <math.h>, que no existe en <cmath>, que estaba #includeing al principio.

Hasen
fuente
2
No soy puro, puedes hacerlo de forma portátil. ¿Quién dice que C ++ requiere IEEE754?
David Heffernan
Solo una nota, 1 oz de prevención es mejor que 1 lb de cura. En otras palabras, evitar que se ejecute 0.f / 0.f es mucho mejor que verificar retroactivamente los nan's en su código. nanEsto puede ser terriblemente destructivo para su programa, si se permite que prolifere, puede introducir errores difíciles de encontrar. Esto se debe a que nanes tóxico, (5 * nan= nan), nanno es igual a nada ( nan! = nan), nanNo es mayor que nada ( nan!> 0), nanno es menor que nada ( nan! <0).
bobobobo
1
@bobobobo: Esa es una característica que permite la verificación centralizada de errores. Al igual que las excepciones frente a los valores de retorno.
Ben Voigt
2
¿Por qué <cmath> no tiene isnan ()? Está en std ::
frankliuao

Respuestas:

351

Según el estándar IEEE, los valores de NaN tienen la extraña propiedad de que las comparaciones que los involucran son siempre falsas. Es decir, para un flotador f, f != fserá verdadero solo si f es NaN.

Tenga en cuenta que, como han señalado algunos comentarios a continuación, no todos los compiladores respetan esto al optimizar el código.

Para cualquier compilador que afirme utilizar el punto flotante IEEE, este truco debería funcionar. Pero no puedo garantizar que va a funcionar en la práctica. Consulte con su compilador, si tiene dudas.

jalf
fuente
44
Es mejor que el compilador no elimine esto si se ejecuta en modo IEEE. Consulte la documentación de su compilador, por supuesto ...
dmckee --- ex-gatito moderador
38
-1 solo funciona en teoría, no en la práctica: compiladores como g ++ (con -fastmath) lo arruinan. La única forma general, hasta c ++ 0x, es probar el patrón de bits.
Saludos y hth. - Alf
66
@Alf: la documentación de la -ffast-mathopción dice explícitamente que puede dar como resultado resultados incorrectos para programas que dependen de una implementación exacta si las reglas / especificaciones IEEE o ISO para funciones matemáticas. Sin esa opción habilitada, el uso x != xes una forma perfectamente válida y portátil de probar NaN.
Adam Rosenfield
77
@ Adam: la documentación declara abiertamente que no es conforme, sí. y sí, me he encontrado con ese argumento antes, discutiendo esto extensamente con Gabriel Dos Reis. se usa comúnmente para defender el diseño, en un argumento circular (no sé si pretendías asociarte a eso, pero vale la pena saberlo: es una guerra de llamas). su conclusión que x != xes válida sin esa opción no sigue lógicamente. Puede ser cierto para una versión particular de g ++, o no. de todos modos, generalmente no tiene forma de garantizar que no se utilizará la opción de matemáticas rápidas.
Saludos y hth. - Alf
77
@Alf: No, no estaba al tanto de su discusión con Gabriel Dos Reis. Steve Jessop hizo un gran punto en la otra pregunta sobre asumir la representación de IEEE. Si asume IEEE 754 y que el compilador está funcionando de manera conforme (es decir, sin la -ffast-mathopción), entonces x != xes una solución válida y portátil. Incluso puede probar -ffast-mathprobando la __FAST_MATH__macro y cambiar a una implementación diferente en ese caso (por ejemplo, usar uniones y twiddling de bits).
Adam Rosenfield
220

No hay ninguna isnan()función disponible en la Biblioteca estándar de C ++ actual. Fue introducido en C99 y definido como una macro, no una función. Los elementos de la biblioteca estándar definidos por C99 no forman parte del estándar actual C ++ ISO / IEC 14882: 1998 ni su actualización ISO / IEC 14882: 2003.

En 2005 se propuso el Informe técnico 1. El TR1 trae compatibilidad con C99 a C ++. A pesar de que nunca se ha adoptado oficialmente para convertirse en estándar C ++, muchas (las implementaciones de GCC 4.0+ o Visual C ++ 9.0+ C ++ proporcionan características TR1, todas ellas o solo algunas (Visual C ++ 9.0 no proporciona funciones matemáticas C99) .

Si TR1 está disponible, entonces cmathincluye elementos C99 como isnan(), isfinite()etc., pero se definen como funciones, no macros, generalmente en el std::tr1::espacio de nombres, aunque muchas implementaciones (es decir, GCC 4+ en Linux o en XCode en Mac OS X 10.5+) los inyectan directamente a std::, por lo que std::isnanestá bien definido.

Además, algunas implementaciones de C ++ todavía hacen que C99 isnan()macro esté disponible para C ++ (incluido a través de cmatho math.h), lo que puede causar más confusión y los desarrolladores pueden asumir que es un comportamiento estándar.

Una nota sobre Viusal C ++, como se mencionó anteriormente, no proporciona std::isnanninguno std::tr1::isnan, pero proporciona una función de extensión definida como la _isnan()que ha estado disponible desde Visual C ++ 6.0

En XCode, hay aún más diversión. Como se mencionó, GCC 4+ define std::isnan. Para versiones anteriores del compilador y la biblioteca de XCode, parece que (aquí hay una discusión relevante ), no he tenido la oportunidad de verificarme) se definen dos funciones, __inline_isnand()en Intel y __isnand()en Power PC.

mloskot
fuente
21
Todos quieren estas funciones como isNan o isInfinity. ¿Por qué las personas a cargo no simplemente incluyen en sus estándares? - Trataré de averiguar cómo ponerme a cargo y votar por esto. Seriamente.
shuhalo
8
@shuhalo A cargo todavía?
Tomáš Zato - Restablece a Monica
11
Esta respuesta debe actualizarse ya std::isnanque ahora forma parte del estándar C ++ 11 y el soporte se ha extendido. std :: isnan fue implementado en Visual Studio comenzando con Visual Studio 2013. Tal vez conseguido @shuhalo a cargo :-)
aberaud
170

Primera solución: si está utilizando C ++ 11

Como se preguntó esto, hubo un poco de nuevos desarrollos: es importante saber que std::isnan()es parte de C ++ 11

Sinopsis

Definido en encabezado <cmath>

bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)

Determina si el número de coma flotante dado arg no es un número ( NaN).

Parámetros

arg: valor de coma flotante

Valor de retorno

truesi arg es NaN, de lo falsecontrario

Referencia

http://en.cppreference.com/w/cpp/numeric/math/isnan

Tenga en cuenta que esto es incompatible con -fast-math si usa g ++, consulte a continuación otras sugerencias.


Otras soluciones: si utiliza herramientas que no cumplen con C ++ 11

Para C99, en C, esto se implementa como una macro isnan(c)que devuelve un valor int. El tipo de xserá flotante, doble o doble largo.

Varios proveedores pueden o no incluir o no una función isnan().

La forma supuestamente portátil de verificar NaNes usar la propiedad IEEE 754 que NaNno es igual a sí misma, es decir x == x, será falsa por xser NaN.

Sin embargo, la última opción puede no funcionar con todos los compiladores y algunas configuraciones (particularmente las configuraciones de optimización), por lo que, en último recurso, siempre puede verificar el patrón de bits ...

BlueTrin
fuente
8
Definitivamente merece ser la respuesta aceptada y merece más votos a favor. Gracias por el consejo
LBes
3
−1 std::isnan sigue siendo una recomendación incorrecta a partir de febrero de 2017, ya que no funciona con la optimización de punto flotante de g ++.
Saludos y hth. - Alf
@ Cheersandhth.-Alf: ¿esta opción cumple con IEEE? La respuesta ha sido editada
BlueTrin
@BlueTrin: Ambos x != xy isnanestán obligados a trabajar para cumplir con IEEE 754. Con respecto a esto último, el estándar IEEE 754-2008 establece que "Las implementaciones proporcionarán las siguientes operaciones no computacionales para todos los formatos aritméticos compatibles" y "isNaN (x) es verdadero si y solo si x es un NaN". Para verificar la conformidad que requiere el estándar is754version1985()y is754version2008(), en su lugar, donde C ++ ofrece std::numeric_limits<Fp>::is_iec559()(IEC 559 es el mismo estándar). Desafortunadamente con la -ffast-mathoptimización, por ejemplo, g ++ afirma conformidad pero no es conforme.
Saludos y hth. - Alf
1
Advertencia: isnan (x) no funciona con la opción -ffinite-math-only en gcc y clang
A Fog
82

También hay una biblioteca de solo encabezado presente en Boost que tiene herramientas ordenadas para lidiar con los tipos de datos de punto flotante

#include <boost/math/special_functions/fpclassify.hpp>

Obtiene las siguientes funciones:

template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);

Si tiene tiempo, eche un vistazo a todo el kit de herramientas matemáticas de Boost, tiene muchas herramientas útiles y está creciendo rápidamente.

Además, cuando se trata de puntos flotantes y no flotantes, puede ser una buena idea mirar las conversiones numéricas .

Anónimo
fuente
1
¡Gracias! Justo lo que estaba buscando.
Dr. Watson
se agregó en Boost 1.35 (acabo de descubrir que mi programa no se compila en la antigua distribución de Linux).
marcin
2
si compila con la opción --fast-math entonces esta función no funcionará como se esperaba.
Gaetano Mendola
43

Hay tres formas de "oficiales": POSIX isnanmacro , C ++ 0x isnanplantilla de función , o Visual C ++ _isnanfunción .

Desafortunadamente, es poco práctico detectar cuál de ellos usar.

Y desafortunadamente, no hay una manera confiable de detectar si tiene representación IEEE 754 con NaNs. La biblioteca estándar ofrece una forma oficial ( numeric_limits<double>::is_iec559). Pero en la práctica, los compiladores como g ++ lo arruinan.

En teoría, uno podría usar simplemente x != x, pero los compiladores como g ++ y visual c ++ lo arruinan.

Por lo tanto, al final, pruebe los patrones de bits NaN específicos , suponiendo (¡y con suerte haciendo cumplir, en algún momento!) Una representación particular como IEEE 754.


EDITAR : como un ejemplo de "compiladores como g ++ ... arruina eso", considera

#include <limits>
#include <assert.h>

void foo( double a, double b )
{
    assert( a != b );
}

int main()
{
    typedef std::numeric_limits<double> Info;
    double const nan1 = Info::quiet_NaN();
    double const nan2 = Info::quiet_NaN();
    foo( nan1, nan2 );
}

Compilación con g ++ (TDM-2 mingw32) 4.4.1:

C: \ test> escriba "C: \ Archivos de programa \ @commands \ gnuc.bat"
@rem -finput-charset = windows-1252
@ g ++ -O -pedantic -std = c ++ 98 -Wall -Wwrite-strings% * -Wno-long-long

C: \ prueba> gnuc x.cpp

C: \ test> a && echo funciona ... || echo! falló
trabajos...

C: \ prueba> gnuc x.cpp --matemáticas rápidas

C: \ test> a && echo funciona ... || echo! falló
Error de aserción: a! = B, archivo x.cpp, línea 6

Esta aplicación ha solicitado el tiempo de ejecución para terminarlo de una manera inusual.
Póngase en contacto con el equipo de soporte de la aplicación para obtener más información.
!ha fallado

C: \ prueba> _
Saludos y hth. - Alf
fuente
44
@Alf: Su ejemplo funciona como se esperaba para mí tanto en Mac OS X como en Linux en varias versiones de g ++ entre 4.0 y 4.5. La documentación de la -ffast-mathopción dice explícitamente que puede dar como resultado resultados incorrectos para programas que dependen de una implementación exacta si las reglas / especificaciones IEEE o ISO para funciones matemáticas. Sin esa opción habilitada, el uso x != xes una forma perfectamente válida y portátil de probar NaN.
Adam Rosenfield
66
@ Adam: lo que te falta es que el estándar C ++ no requiere representación IEEE o matemática para las carrozas. Por lo que dice la página del manual, gcc -ffast-mathsigue siendo una implementación de C ++ conforme (bueno, suponiendo que sea numeric_limits::is_iec559correcta, lo es, aunque Alf sugiere que no): el código de C ++ que se basa en IEEE no es C ++ portátil y no tiene derecho esperar implementaciones para proporcionarlo.
Steve Jessop
55
Y Alf tiene razón, prueba rápida en gcc 4.3.4 y is_iec559es cierto con -ffast-math. Entonces, el problema aquí es que los documentos de GCC -ffast-mathsolo dicen que no es IEEE / ISO para las funciones matemáticas, mientras que deberían decir que no es C ++, porque su implementación numeric_limitsestá simplificada. Supongo que GCC no siempre puede decir en el momento en que se define la plantilla, si el backend eventual realmente tiene flotadores conformes, por lo que ni siquiera lo intenta. IIRC hay problemas similares en la lista de errores pendientes para la conformidad C99 de GCC.
Steve Jessop
1
@Alf, @Steve, no sabía que el estándar C ++ no tiene especificación sobre valores de punto flotante. Es bastante impactante para mí. Se ve mejor manejando IEEE 754 y NaN como una extensión específica de plataforma en lugar de estándar. ¿No es así? ¿Y puedo esperar cualquier tipo de isnan () o IEEE754 agregado en C ++ 0x?
Eonil
3
@Eonil: C ++ 0x todavía tiene, por ejemplo, "La representación del valor de los tipos de punto flotante está definida por la implementación". C y C ++ tienen como objetivo admitir implementaciones en máquinas sin hardware de punto flotante, y los flotadores IEEE 754 adecuados pueden ser bastante más lentos de emular que las alternativas razonablemente precisas. La teoría es que puede afirmar is_iec559si necesita IEEE, en la práctica eso no parece funcionar en GCC. C ++ 0x tiene una isnanfunción, pero dado que GCC no se implementa correctamente is_iec559ahora, supongo que tampoco lo hará en C ++ 0x, y -ffast-mathbien podría romperlo isnan.
Steve Jessop
39

Hay un std :: isnan si su compilador admite extensiones c99, pero no estoy seguro de si mingw lo hace.

Aquí hay una pequeña función que debería funcionar si su compilador no tiene la función estándar:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}
CTT
fuente
66
¿Por qué no solo var! = var?
Brian R. Bondy
8
Al hacerlo, existe la posibilidad de que el compilador optimice la comparación, siempre regresando verdadero.
CTT
23
No, no lo hay. Un compilador que hace eso está roto. También podría decir que existe la posibilidad de que la biblioteca estándar isnandevuelva el resultado incorrecto. Técnicamente cierto, el compilador podría tener errores, pero en la práctica, no va a suceder. Igual que var != var. Funciona porque así es como se definen los valores de punto flotante IEEE.
jalf
29
si se establece -ffast-math, isnan () no podrá devolver el resultado correcto para gcc. Por supuesto, esta optimización está documentada como rompiendo la semántica de IEEE ...
Matthew Herrmann
Si se establece -ffast-math, entonces el compilador tiene errores. O, más bien, si se establece -ffast-math, todas las apuestas están apagadas y de todos modos no puede confiar en NaNs.
Adrian Ratnapala
25

Puede usar numeric_limits<float>::quiet_NaN( )definido en la limitsbiblioteca estándar para probar. Hay una constante separada definida para double.

#include <iostream>
#include <math.h>
#include <limits>

using namespace 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;
   }

   return 0;
}

No sé si esto funciona en todas las plataformas, ya que solo probé con g ++ en Linux.

Bill el lagarto
fuente
2
Sin embargo, tenga cuidado: parece haber un error en numeric_limits en GCC versión 3.2.3, ya que devuelve 0.0 para quiet_NaN. Las versiones posteriores de GCC están bien en mi experiencia.
Nathan Kitchen
@Nathan: Es bueno saberlo. Estoy usando la versión 4.3.2, así que estoy fuera de peligro.
Bill the Lizard
18

Puede usar la isnan()función, pero debe incluir la biblioteca matemática de C.

#include <cmath>

Como esta función es parte de C99, no está disponible en todas partes. Si su proveedor no proporciona la función, también puede definir su propia variante para la compatibilidad.

inline bool isnan(double x) {
    return x != x;
}
raimue
fuente
¡Estaba usando <cmath> y no hay isnan en él! dicho sea de paso descubrí que no es una isnanen <math.h>
Hasen
1
Como dije, esto es parte de C99. Como C99 no es parte de ningún estándar actual de C ++, proporcioné la alternativa. Pero como es probable que isnan () se incluya en un próximo estándar de C ++, puse una directiva #ifndef a su alrededor.
raimue
12

El siguiente código utiliza la definición de NAN (conjunto de bits de exponente, al menos un conjunto de bits fraccional) y supone que sizeof (int) = sizeof (float) = 4. Puede buscar NAN en Wikipedia para obtener más detalles.

bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }

Ian
fuente
Creo que esto también funcionaría en grandes plataformas endian. El literal 0x7fffffffsimplemente se quedaría en la memoria como ff ff ff 7f. valuetiene el mismo orden que tiene 0x7f800000, por lo que todas las operaciones se alinean (no hay intercambio de bytes). Me interesaría si alguien pudiera probar esto en una gran plataforma endian.
Bryan W. Wagner
0x7fff1234También es un NaN. Así es0xffffffff
Steve Hollasch
12

prevención nan

Mi respuesta a esta pregunta es no usar controles retroactivos paranan . Utilice controles preventivos para las divisiones del formulario en su 0.0/0.0lugar.

#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.

nanresultados de la operación 0.f/0.f, o 0.0/0.0. nanEs un terrible enemigo de la estabilidad de su código que debe ser detectado y evitado con mucho cuidado 1 . Las propiedades de naneso son diferentes de los números normales:

  • nanes tóxico, (5 * nan= nan)
  • nanno es igual a nada, ni siquiera a sí mismo ( nan! = nan)
  • nanno mayor que nada ( nan!> 0)
  • nanno es menos que nada ( nan! <0)

Las últimas 2 propiedades enumeradas son contra-lógicas y darán como resultado un comportamiento extraño del código que se basa en comparaciones con un nannúmero (la 3ra última propiedad también es extraña pero probablemente nunca verá x != x ?en su código (a menos que esté verificando) para nan (poco confiable))).

En mi propio código, noté que los nanvalores tienden a producir errores difíciles de encontrar. (Tenga en cuenta que este no es el caso para info -inf. ( -inf<0) devuelve TRUE, (0 < inf) devuelve VERDADERO e incluso ( -inf< inf) devuelve VERDADERO. Entonces, en mi experiencia, el comportamiento del código a menudo sigue siendo el deseado).

que hacer bajo nan

Lo que desea que ocurra 0.0/0.0 debe manejarse como un caso especial , pero lo que haga debe depender de los números que espera obtener del código.

En el ejemplo anterior, el resultado de ( 0.f/FLT_MIN) será 0, básicamente. Es posible que desee 0.0/0.0generar en su HUGElugar. Entonces,

float x=0.f, y=0.f, z;
if( !x && !y )    // 0.f/0.f case
  z = FLT_MAX ;   // biggest float possible
else
  z = y/x ;       // regular division.

Entonces, en lo anterior, si x fuera 0.f, infresultaría (que tiene un comportamiento bastante bueno / no destructivo como se mencionó anteriormente en realidad).

Recuerde, la división de enteros por 0 causa una excepción de tiempo de ejecución . Por lo tanto, siempre debe verificar la división de enteros por 0. El hecho de que 0.0/0.0se evalúe silenciosamente nanno significa que pueda ser vago y no verificar 0.0/0.0antes de que suceda.

1 Las comprobaciones de nanvía a x != xveces no son confiables ( x != xalgunos compiladores optimizadores las eliminan y rompen el cumplimiento de IEEE, específicamente cuando el -ffast-mathconmutador está habilitado).

bobobobo
fuente
Gracias por señalar esto; Una programación como esa definitivamente ayudaría con el problema como tal. Pero la próxima vez, intente no abusar demasiado de las funciones de formato de texto. Cambiar el tamaño de fuente, el peso y el estilo de este tipo hacen que sea realmente difícil de leer.
Magnus
44
Tenga en cuenta que 0.0 / 0.0 no es la única operación que podría resultar en un NaN. La raíz cuadrada de un número negativo devuelve NaN. El coseno de + infinito también devuelve NaN. la operación acos (x) donde x no está en el rango [0, pi] también puede dar como resultado NaN. En pocas palabras, hay que tener mucho cuidado para observar también estas operaciones potencialmente riesgosas, no solo a 0.0 / 0.0.
Boris Dalstein
Totalmente de acuerdo con Boris. En mi experiencia, NaN prácticamente siempre proviene de algo así como sqrt (-1,302e-53), es decir, los resultados de cálculo intermedios cercanos a cero se introducen en sqrt sin verificar la negatividad.
hans_meine
1
"Prevenir los NaN" significa que necesita entrar en todas las operaciones aritméticas básicas, no solo en la división. Deberá tener cuidado con ∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0, entre muchos otros. Ser "preventivo" con operaciones aritméticas tan básicas significa que limitará por completo su rendimiento (y probablemente perderá casos adicionales en los que no pensó).
Steve Hollasch
11

A partir de C ++ 14, hay varias formas de probar si un número de coma flotante value es un NaN.

De estas formas, solo la comprobación de los bits de la representación del número funciona de manera confiable, como se señaló en mi respuesta original. En particular, std::isnany la verificación propuesta a menudo v != v, no funciona de manera confiable y no debe usarse, para que su código deje de funcionar correctamente cuando alguien decida que se necesita optimización de punto flotante y le pide al compilador que lo haga. Esta situación puede cambiar, los compiladores pueden ser más conformes, pero para este problema que no ha sucedido en los 6 años desde la respuesta original.

Durante unos 6 años, mi respuesta original fue la solución seleccionada para esta pregunta, que estaba bien. Pero recientemente v != vse ha seleccionado una respuesta altamente votada que recomienda la prueba poco confiable . De ahí esta respuesta adicional más actualizada (ahora tenemos los estándares C ++ 11 y C ++ 14, y C ++ 17 en el horizonte).


Las principales formas de verificar la NaN-ness, a partir de C ++ 14, son:

  • std::isnan(value) )
    es la forma de biblioteca estándar prevista desde C ++ 11. isnanaparentemente entra en conflicto con la macro Posix del mismo nombre, pero en la práctica eso no es un problema. El problema principal es que cuando se solicita la optimización aritmética de punto flotante, con al menos un compilador principal, a saber, g ++,std::isnan devuelve el falseargumento NaN .

  • (fpclassify(value) == FP_NAN) )
    Sufre el mismo problema que std::isnan, es decir, no es confiable.

  • (value != value) )
    Recomendado en muchas respuestas SO. Sufre del mismo problema questd::isnan , es decir, no es confiable.

  • (value == Fp_info::quiet_NaN()) )
    Esta es una prueba que con el comportamiento estándar no debería detectar NaNs, pero que con el comportamiento optimizado podría detectar NaNs (debido a que el código optimizado solo compara las representaciones de nivel de bit directamente), y tal vez combinado con otra forma de cubrir el comportamiento no optimizado estándar , podría detectar NaN de manera confiable. Desafortunadamente resultó no funcionar de manera confiable.

  • (ilogb(value) == FP_ILOGBNAN) )
    Sufre del mismo problema que std::isnan, es decir, no es confiable.

  • isunordered(1.2345, value) )
    Sufre del mismo problema que std::isnan, es decir, no es confiable.

  • is_ieee754_nan( value ) )
    Esta no es una función estándar. Está comprobando los bits de acuerdo con el estándar IEEE 754. Es completamente confiable pero el código depende de alguna manera del sistema.


En el siguiente código de prueba completo, "éxito" es si una expresión informa Nanness del valor. Para la mayoría de las expresiones, esta medida de éxito, el objetivo de detectar NaNs y solo NaNs, corresponde a su semántica estándar. Para el(value == Fp_info::quiet_NaN()) )Sin embargo, expresión, el comportamiento estándar es que no funciona como un detector de NaN.

#include <cmath>        // std::isnan, std::fpclassify
#include <iostream>
#include <iomanip>      // std::setw
#include <limits>
#include <limits.h>     // CHAR_BIT
#include <sstream>
#include <stdint.h>     // uint64_t
using namespace std;

#define TEST( x, expr, expected ) \
    [&](){ \
        const auto value = x; \
        const bool result = expr; \
        ostringstream stream; \
        stream << boolalpha << #x " = " << x << ", (" #expr ") = " << result; \
        cout \
            << setw( 60 ) << stream.str() << "  " \
            << (result == expected? "Success" : "FAILED") \
            << endl; \
    }()

#define TEST_ALL_VARIABLES( expression ) \
    TEST( v, expression, true ); \
    TEST( u, expression, false ); \
    TEST( w, expression, false )

using Fp_info = numeric_limits<double>;

inline auto is_ieee754_nan( double const x )
    -> bool
{
    static constexpr bool   is_claimed_ieee754  = Fp_info::is_iec559;
    static constexpr int    n_bits_per_byte     = CHAR_BIT;
    using Byte = unsigned char;

    static_assert( is_claimed_ieee754, "!" );
    static_assert( n_bits_per_byte == 8, "!" );
    static_assert( sizeof( x ) == sizeof( uint64_t ), "!" );

    #ifdef _MSC_VER
        uint64_t const bits = reinterpret_cast<uint64_t const&>( x );
    #else
        Byte bytes[sizeof(x)];
        memcpy( bytes, &x, sizeof( x ) );
        uint64_t int_value;
        memcpy( &int_value, bytes, sizeof( x ) );
        uint64_t const& bits = int_value;
    #endif

    static constexpr uint64_t   sign_mask       = 0x8000000000000000;
    static constexpr uint64_t   exp_mask        = 0x7FF0000000000000;
    static constexpr uint64_t   mantissa_mask   = 0x000FFFFFFFFFFFFF;

    (void) sign_mask;
    return (bits & exp_mask) == exp_mask and (bits & mantissa_mask) != 0;
}

auto main()
    -> int
{
    double const v = Fp_info::quiet_NaN();
    double const u = 3.14;
    double const w = Fp_info::infinity();

    cout << boolalpha << left;
    cout << "Compiler claims IEEE 754 = " << Fp_info::is_iec559 << endl;
    cout << endl;;
    TEST_ALL_VARIABLES( std::isnan(value) );                    cout << endl;
    TEST_ALL_VARIABLES( (fpclassify(value) == FP_NAN) );        cout << endl;
    TEST_ALL_VARIABLES( (value != value) );                     cout << endl;
    TEST_ALL_VARIABLES( (value == Fp_info::quiet_NaN()) );      cout << endl;
    TEST_ALL_VARIABLES( (ilogb(value) == FP_ILOGBNAN) );        cout << endl;
    TEST_ALL_VARIABLES( isunordered(1.2345, value) );           cout << endl;
    TEST_ALL_VARIABLES( is_ieee754_nan( value ) );
}

Resultados con g ++ (tenga en cuenta de nuevo que el comportamiento estándar de (value == Fp_info::quiet_NaN())es que no funciona como un detector de NaN, solo es de gran interés práctico aquí):

[C: \ my \ forum \ so \ 282 (detectar NaN)]
>  g ++ --version | encontrar "++"
g ++ (x86_64-win32-sjlj-rev1, construido por el proyecto MinGW-W64) 6.3.0

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> g ++ foo.cpp && a
El compilador afirma IEEE 754 = verdadero

v = nan, (std :: isnan (valor)) = verdadero éxito
u = 3.14, (std :: isnan (valor)) = falso Éxito
w = inf, (std :: isnan (valor)) = falso Éxito

v = nan, ((fpclassify (value) == 0x0100)) = verdadero Éxito
u = 3.14, ((fpclassify (value) == 0x0100)) = falso Éxito
w = inf, ((fpclassify (value) == 0x0100)) = falso Éxito

v = nan, ((valor! = valor)) = verdadero Éxito
u = 3.14, ((valor! = valor)) = falso Éxito
w = inf, ((valor! = valor)) = falso Éxito

v = nan, ((valor == Fp_info :: quiet_NaN ())) = falso FALLIDO
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = falso Éxito
w = inf, ((valor == Fp_info :: quiet_NaN ())) = falso Éxito

v = nan, ((ilogb (value) == ((int) 0x80000000))) = verdadero éxito
u = 3.14, ((ilogb (valor) == ((int) 0x80000000))) = falso Éxito
w = inf, ((ilogb (value) == ((int) 0x80000000))) = falso Éxito

v = nan, (no está ordenado (1.2345, valor)) = verdadero Éxito
u = 3.14, (no está ordenado (1.2345, valor)) = falso Éxito
w = inf, (no está ordenado (1.2345, valor)) = falso Éxito

v = nan, (is_ieee754_nan (value)) = verdadero Éxito
u = 3.14, (is_ieee754_nan (valor)) = falso Éxito
w = inf, (is_ieee754_nan (valor)) = falso Éxito

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> g ++ foo.cpp -ffast-math && a
El compilador afirma IEEE 754 = verdadero

v = nan, (std :: isnan (valor)) = falso FALLIDO
u = 3.14, (std :: isnan (valor)) = falso Éxito
w = inf, (std :: isnan (valor)) = falso Éxito

v = nan, ((fpclassify (value) == 0x0100)) = falso FALLIDO
u = 3.14, ((fpclassify (value) == 0x0100)) = falso Éxito
w = inf, ((fpclassify (value) == 0x0100)) = falso Éxito

v = nan, ((valor! = valor)) = falso FALLIDO
u = 3.14, ((valor! = valor)) = falso Éxito
w = inf, ((valor! = valor)) = falso Éxito

v = nan, ((valor == Fp_info :: quiet_NaN ())) = verdadero Éxito
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = verdadero FALLIDO
w = inf, ((valor == Fp_info :: quiet_NaN ())) = verdadero FALLIDO

v = nan, ((ilogb (value) == ((int) 0x80000000))) = verdadero éxito
u = 3.14, ((ilogb (valor) == ((int) 0x80000000))) = falso Éxito
w = inf, ((ilogb (value) == ((int) 0x80000000))) = falso Éxito

v = nan, (no está ordenado (1.2345, valor)) = falso FALLIDO
u = 3.14, (no está ordenado (1.2345, valor)) = falso Éxito
w = inf, (no está ordenado (1.2345, valor)) = falso Éxito

v = nan, (is_ieee754_nan (value)) = verdadero Éxito
u = 3.14, (is_ieee754_nan (valor)) = falso Éxito
w = inf, (is_ieee754_nan (valor)) = falso Éxito

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> _

Resultados con Visual C ++:

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> cl / nologo- 2> y 1 | encontrar "++"
Microsoft (R) C / C ++ Optimizing Compiler versión 19.00.23725 para x86

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> cl foo.cpp / Feb && b
foo.cpp
El compilador afirma IEEE 754 = verdadero

v = nan, (std :: isnan (valor)) = verdadero éxito
u = 3.14, (std :: isnan (valor)) = falso Éxito
w = inf, (std :: isnan (valor)) = falso Éxito

v = nan, ((fpclassify (value) == 2)) = verdadero éxito
u = 3.14, ((fpclassify (value) == 2)) = falso Éxito
w = inf, ((fpclassify (value) == 2)) = falso Éxito

v = nan, ((valor! = valor)) = verdadero Éxito
u = 3.14, ((valor! = valor)) = falso Éxito
w = inf, ((valor! = valor)) = falso Éxito

v = nan, ((valor == Fp_info :: quiet_NaN ())) = falso FALLIDO
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = falso Éxito
w = inf, ((valor == Fp_info :: quiet_NaN ())) = falso Éxito

v = nan, ((ilogb (value) == 0x7fffffff)) = verdadero Éxito
u = 3.14, ((ilogb (valor) == 0x7fffffff)) = falso Éxito
w = inf, ((ilogb (valor) == 0x7fffffff)) = verdadero FALLIDO

v = nan, (no está ordenado (1.2345, valor)) = verdadero Éxito
u = 3.14, (no está ordenado (1.2345, valor)) = falso Éxito
w = inf, (no está ordenado (1.2345, valor)) = falso Éxito

v = nan, (is_ieee754_nan (value)) = verdadero Éxito
u = 3.14, (is_ieee754_nan (valor)) = falso Éxito
w = inf, (is_ieee754_nan (valor)) = falso Éxito

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> cl foo.cpp / Feb / fp: rápido && b
foo.cpp
El compilador afirma IEEE 754 = verdadero

v = nan, (std :: isnan (valor)) = verdadero éxito
u = 3.14, (std :: isnan (valor)) = falso Éxito
w = inf, (std :: isnan (valor)) = falso Éxito

v = nan, ((fpclassify (value) == 2)) = verdadero éxito
u = 3.14, ((fpclassify (value) == 2)) = falso Éxito
w = inf, ((fpclassify (value) == 2)) = falso Éxito

v = nan, ((valor! = valor)) = verdadero Éxito
u = 3.14, ((valor! = valor)) = falso Éxito
w = inf, ((valor! = valor)) = falso Éxito

v = nan, ((valor == Fp_info :: quiet_NaN ())) = falso FALLIDO
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = falso Éxito
w = inf, ((valor == Fp_info :: quiet_NaN ())) = falso Éxito

v = nan, ((ilogb (value) == 0x7fffffff)) = verdadero Éxito
u = 3.14, ((ilogb (valor) == 0x7fffffff)) = falso Éxito
w = inf, ((ilogb (valor) == 0x7fffffff)) = verdadero FALLIDO

v = nan, (no está ordenado (1.2345, valor)) = verdadero Éxito
u = 3.14, (no está ordenado (1.2345, valor)) = falso Éxito
w = inf, (no está ordenado (1.2345, valor)) = falso Éxito

v = nan, (is_ieee754_nan (value)) = verdadero Éxito
u = 3.14, (is_ieee754_nan (valor)) = falso Éxito
w = inf, (is_ieee754_nan (valor)) = falso Éxito

[C: \ my \ forum \ so \ 282 (detectar NaN)]
> _

Resumiendo los resultados anteriores, solo las pruebas directas de la representación a nivel de bit, utilizando la is_ieee754_nanfunción definida en este programa de prueba, funcionaron de manera confiable en todos los casos tanto con g ++ como con Visual C ++.


Anexo:
Después de publicar lo anterior, me di cuenta de otra posible prueba para NaN, mencionada en otra respuesta aquí, a saber ((value < 0) == (value >= 0)). Resultó funcionar bien con Visual C ++ pero falló con la -ffast-mathopción de g ++ . Solo las pruebas directas de patrones de bits funcionan de manera confiable.

Saludos y hth. - Alf
fuente
7
inline bool IsNan(float f)
{
    const uint32 u = *(uint32*)&f;
    return (u&0x7F800000) == 0x7F800000 && (u&0x7FFFFF);    // Both NaN and qNan.
}

inline bool IsNan(double d)
{
    const uint64 u = *(uint64*)&d;
    return (u&0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (u&0xFFFFFFFFFFFFFULL);
}

Esto funciona si sizeof(int)es 4 y sizeof(long long)es 8.

Durante el tiempo de ejecución es solo una comparación, los moldes no toman tiempo. Simplemente cambia la configuración de las banderas de comparación para verificar la igualdad.

ST3
fuente
También tenga en cuenta que está limitado a la representación IEEE 754.
Saludos y hth. - Alf
Tenga en cuenta que este reparto rompe la estricta regla de alias de g ++, y que se sabe que el compilador hace Unmentionable Things ™ cuando detecta UB formal. En lugar de conversiones eficientes, con g ++ debe usar memcpy, a través de una matriz de bytes, para estar seguro. Codifique eso en mi respuesta # 2 .
Saludos y hth. - Alf
4

Una posible solución que no dependería de la representación IEEE específica para NaN utilizada sería la siguiente:

template<class T>
bool isnan( T f ) {
    T _nan =  (T)0.0/(T)0.0;
    return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}
Dan Nathan
fuente
El punto flotante de precisión simple tiene más de 8 millones de representaciones de bits legítimas y diferentes para NaN, por lo que deberá agregar algunas comparaciones más. :)
Steve Hollasch
4

Teniendo en cuenta que (x! = X) no siempre está garantizado para NaN (como si usara la opción -ffast-math), he estado usando:

#define IS_NAN(x) (((x) < 0) == ((x) >= 0))

Los números no pueden ser ambos <0 y> = 0, por lo que realmente esta verificación solo pasa si el número no es menor, ni mayor o igual que cero. Que básicamente no es ningún número, o NaN.

También puede usar esto si prefiere:

#define IS_NAN(x) (!((x)<0) && !((x)>=0)

Sin embargo, no estoy seguro de cómo esto se ve afectado por las matemáticas rápidas, por lo que su millaje puede variar.

Jerramy
fuente
Esto es realmente defectuoso de la misma manera que f != fes defectuoso también. He visto llvm optimizando una pieza de código casi idéntica. El optimizador puede propagar la información sobre la primera comparación y descubrir que la segunda comparación puede nunca ser cierta si la primera lo es. (si el compilador obedece estrictamente las reglas de IEEE f != fes mucho más simple de todos modos)
Markus
No funciona con la -ffast-mathopción de g ++ . Funciona con Visual C ++. Ver ( stackoverflow.com/a/42138465/464581 ).
Saludos y hth. - Alf
3

En cuanto a mí, la solución podría ser una macro para hacerlo explícitamente en línea y, por lo tanto, lo suficientemente rápido. También funciona para cualquier tipo de flotador. Se basa en el hecho de que el único caso cuando un valor no es igual a sí mismo es cuando el valor no es un número.

#ifndef isnan
  #define isnan(a) (a != a)
#endif
usuario1705817
fuente
¡Esta es una de las mejores respuestas a esta pregunta! Gracias por compartir.
Henri Menke
2
Otras respuestas indican que esto puede fallar con el conjunto de opciones -ffast-math.
Technophile
3

Esto funciona:

#include <iostream>
#include <math.h>
using namespace std;

int main ()
{
  char ch='a';
  double val = nan(&ch);
  if(isnan(val))
     cout << "isnan" << endl;

  return 0;
}

salida: isnan

edW
fuente
1

Me parece que el mejor enfoque verdaderamente multiplataforma sería usar una unión y probar el patrón de bits del doble para verificar si hay NaN.

No he probado a fondo esta solución, y puede haber una forma más eficiente de trabajar con los patrones de bits, pero creo que debería funcionar.

#include <stdint.h>
#include <stdio.h>

union NaN
{
    uint64_t bits;
    double num;
};

int main()
{
    //Test if a double is NaN
    double d = 0.0 / 0.0;
    union NaN n;
    n.num = d;
    if((n.bits | 0x800FFFFFFFFFFFFF) == 0xFFFFFFFFFFFFFFFF)
    {
        printf("NaN: %f", d);
    }

    return 0;
}
Sheldon Juncker
fuente
Tenga en cuenta que "es un comportamiento indefinido leer del miembro del sindicato que no se escribió más recientemente". Por lo tanto, este uso de un unionjuego de palabras entre dos tipos puede no funcionar como se desea (: sad_panda :). La forma correcta (aunque en realidad no es tan portátil como se desea) sería evitar la unión por completo y hacer una memoria desde doubleuna uint64_tvariable diferente , luego hacer la prueba usando esa variable auxiliar.
Eljay
0

En x86-64 puede tener métodos extremadamente rápidos para verificar NaN e infinito, que funcionan independientemente de la -ffast-mathopción del compilador. ( f != f, std::isnan, std::isinfSiempre producen falsecon -ffast-math).


Las pruebas de NaN, infinito y números finitos se pueden hacer fácilmente al verificar el exponente máximo. infinito es máximo exponente con cero mantisa, NaN es máximo exponente y no cero mantisa. El exponente se almacena en los siguientes bits después del bit de signo superior, de modo que simplemente podemos desplazarnos a la izquierda para deshacernos del bit de signo y hacer que el exponente sea el bit superior, no operator&es necesario enmascarar ( ):

static inline uint64_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;
}

static inline uint32_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;
}

constexpr uint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);
constexpr uint32_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.
static inline bool isnan2(double a)    { return load_ieee754_rep(a) << 1  > inf_double_shl1; }
static inline bool isinf2(double a)    { return load_ieee754_rep(a) << 1 == inf_double_shl1; }
static inline bool isfinite2(double a) { return load_ieee754_rep(a) << 1  < inf_double_shl1; }
static inline bool isnan2(float a)     { return load_ieee754_rep(a) << 1  > inf_float_shl1; }
static inline bool isinf2(float a)     { return load_ieee754_rep(a) << 1 == inf_float_shl1; }
static inline bool isfinite2(float a)  { return load_ieee754_rep(a) << 1  < inf_float_shl1; }

Las stdversiones de isinfyisfinite cargan 2 double/floatconstantes del .datasegmento y, en el peor de los casos, pueden causar 2 errores de caché de datos. Las versiones anteriores no cargan ningún dato, inf_double_shl1y las inf_float_shl1constantes se codifican como operandos inmediatos en las instrucciones de ensamblaje.


Más rápido isnan2son solo 2 instrucciones de montaje:

bool isnan2(double a) {
    bool r;
    asm(".intel_syntax noprefix"
        "\n\t ucomisd %1, %1"
        "\n\t setp %b0"
        "\n\t .att_syntax prefix"
        : "=g" (r)
        : "x" (a)
        : "cc"
        );
    return r;
}

Utiliza el hecho de que la ucomisdinstrucción establece el indicador de paridad si algún argumento es NaN. Así es como std::isnanfunciona cuando no -ffast-mathse especifica ninguna opción.

Maxim Egorushkin
fuente
-1

El estándar IEEE dice que cuando el exponente es todo 1sy la mantisa no es cero, el número es a NaN. Doble es 1bit de signo, 11bits de exponente y 52bits de mantisa. Haz un poco de verificación.

golpear
fuente
-3

Como los comentarios anteriores indican a! = A no funcionará en g ++ y algunos otros compiladores, pero este truco debería funcionar. Puede que no sea tan eficiente, pero sigue siendo una forma:

bool IsNan(float a)
{
    char s[4];
    sprintf(s, "%.3f", a);
    if (s[0]=='n') return true;
    else return false;
}

Básicamente, en g ++ (aunque no estoy seguro de otros) printf imprime 'nan' en formatos% d o% .f si la variable no es un entero / flotante válido. Por lo tanto, este código verifica que el primer carácter de la cadena sea 'n' (como en "nan")

ZenJ
fuente
2
¿No causaría eso un desbordamiento del búfer si a = 234324.0f?
Mazyod
Sí, o 340282346638528859811704183484516925440.000si a = FLT_MAX. Tendría que usar char s[7]; sprintf(s, "%.0g", a);, que serán 6 canales si a=-FLT_MAX, o-3e+38
bobobobo
-3

Esto detecta el infinito y también NaN en Visual Studio comprobando que está dentro de los límites dobles:

//#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;
mathengineer
fuente
Verifique la definición de FLT_MIN, DBL_MINy LDBL_MINmás cuidadosamente. Estos se definen como los valores normalizados más pequeños para cada tipo. Por ejemplo, la precisión simple tiene más de 8 millones de valores denorm legítimos que son mayores que cero y menores que FLT_MIN(y no son NaN).
Steve Hollasch