Verificando se um double (ou float) é NaN em C ++

369

Existe uma função isnan ()?

PS: Estou no MinGW (se isso faz diferença).

Eu resolvi isso usando isnan () from <math.h>, o qual não existe <cmath>, no qual eu estava #includeinicialmente.

hasen
fonte
2
Eu não sou puro, você pode fazê-lo portably. Quem disse que o C ++ requer IEEE754?
David Heffernan
Apenas uma nota, 1 oz de prevenção é melhor do que 1 lb de cura. Em outras palavras, impedir que 0.f / 0.f seja executado é muito melhor do que procurar retroativamente por nans no seu código. nanIsso pode ser terrivelmente destrutivo para o seu programa; se for permitido proliferar, pode ser introduzido bugs difíceis de encontrar. Isso ocorre porque nané tóxico (5 * nan= nan), nannão é igual a nada ( nan! = nan), nanNão é maior que nada ( nan!> 0), nannão é menor que qualquer coisa ( nan! <0).
bobobobo
11
@obobobo: Esse é um recurso que permite a verificação centralizada de erros. Assim como exceções versus valores de retorno.
Ben Voigt
2
Por que <cmath> não possui isnan ()? Está em std ::
frankliuao 2/17/17

Respostas:

350

De acordo com o padrão IEEE, os valores de NaN têm a propriedade ímpar de que as comparações que os envolvem são sempre falsas. Ou seja, para um float f, f != fserá verdadeiro apenas se f for NaN.

Observe que, como alguns comentários abaixo apontaram, nem todos os compiladores respeitam isso ao otimizar o código.

Para qualquer compilador que alega usar ponto flutuante IEEE, esse truque deve funcionar. Mas eu não posso garantir que ele vai funcionar na prática. Verifique com seu compilador, em caso de dúvida.

jalf
fonte
4
É melhor o compilador não remover isso se estiver executando no modo IEEE. Verifique a documentação do seu compilador, é claro ...
dmckee --- ex-moderator kitten
38
-1 só funciona na teoria, não na prática: compiladores como o g ++ (com -fastmath) estragam tudo. a única maneira geral, até c ++ 0x, é testar o padrão de bits.
Saúde e hth. - Alf
66
@ Alf: A documentação da -ffast-mathopção diz explicitamente que pode resultar em saída incorreta para programas que dependem de uma implementação exata se as regras / especificações IEEE ou ISO para funções matemáticas. Sem essa opção ativada, o uso x != xé uma maneira perfeitamente válida e portátil de testar o NaN.
Adam Rosenfield 26/03
7
@ Adam: a documentação afirma abertamente que não está em conformidade, sim. e sim, eu já encontrei esse argumento antes, discutindo isso com Gabriel Dos Reis. é comumente usado para defender o design, em um argumento circular (não sei se você pretendia se associar a isso, mas vale a pena conhecer - é coisa de guerra de chamas). sua conclusão que x != xé válida sem essa opção não segue logicamente. pode ser verdade para uma versão específica do g ++ ou não. de qualquer maneira, você geralmente não tem como garantir que a opção fastmath não será usada.
Saúde e hth. - Alf
7
@ Alf: Não, eu não estava ciente de sua discussão com Gabriel Dos Reis. Steve Jessop fez um grande ponto na outra pergunta sobre assumir a representação do IEEE. Se você assumir o IEEE 754 e se o compilador estiver operando de maneira compatível (ou seja, sem a -ffast-mathopção), x != xserá uma solução válida e portátil. Você pode até testar -ffast-mathtestando a __FAST_MATH__macro e alternar para uma implementação diferente nesse caso (por exemplo, use uniões e ajuste de bits).
Adam Rosenfield 27/03
220

Não há nenhuma isnan()função disponível na biblioteca padrão do C ++ atual. Foi introduzido em C99 e definido como uma macro não uma função. Os elementos da biblioteca padrão definidos por C99 não fazem parte do atual padrão C ++ ISO / IEC 14882: 1998 nem de sua atualização ISO / IEC 14882: 2003.

Em 2005, o Relatório Técnico 1 foi proposto. O TR1 traz compatibilidade com C99 para C ++. Apesar de nunca ter sido oficialmente adotado para se tornar o padrão C ++, muitas implementações ( GCC 4.0+ ou Visual C ++ 9.0+ C ++ fornecem recursos TR1, todos eles ou apenas alguns (o Visual C ++ 9.0 não fornece funções matemáticas C99) .

Se TR1 estiver disponível, ele cmathincluirá elementos C99 como isnan(), isfinite()etc., mas eles serão definidos como funções, não macros, geralmente no std::tr1::espaço para nome, embora muitas implementações (por exemplo, GCC 4+ no Linux ou XCode no Mac OS X 10.5+) os injetem. diretamente para std::, portanto, std::isnanestá bem definido.

Além disso, algumas implementações de C ++ ainda disponibilizam a isnan()macro C99 para C ++ (incluída por cmathou math.h), o que pode causar mais confusões e os desenvolvedores podem assumir que é um comportamento padrão.

Uma observação sobre o Viusal C ++, como mencionado acima, não fornece std::isnannenhum std::tr1::isnan, mas fornece uma função de extensão definida como _isnan()disponível desde o Visual C ++ 6.0

No XCode, há ainda mais diversão. Como mencionado, o GCC 4+ define std::isnan. Para versões mais antigas do compilador e do formulário de biblioteca XCode, parece (aqui está uma discussão relevante ), não tive chance de me verificar) duas funções são definidas, __inline_isnand()na Intel e __isnand()no Power PC.

mloskot
fonte
21
Todo mundo quer essas funções como isNan ou isInfinity. Por que os responsáveis ​​não incluem simplesmente em seus padrões ???? - Vou tentar descobrir como me encarregar e colocar meu voto nisso. A sério.
shuhalo
8
@shuhalo Já está no comando?
Tomáš Zato - Restabelece Monica
11
Esta resposta deve ser atualizada, pois std::isnanagora faz parte do padrão C ++ 11 e o suporte foi espalhado. std :: isnan foi implementado em Visual Studio começando com Visual Studio 2013. Talvez @shuhalo ficou a cargo :-)
aberaud
170

Primeira solução: se você estiver usando C ++ 11

Como isso foi solicitado, surgiram alguns desenvolvimentos: é importante saber que std::isnan()faz parte do C ++ 11

Sinopse

Definido no cabeçalho <cmath>

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

Determina se o número de ponto flutuante arg especificado não é um número ( NaN).

Parâmetros

arg: valor do ponto flutuante

Valor de retorno

truese arg for NaN, falsecaso contrário

Referência

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

Observe que isso é incompatível com -fast-math se você usa o g ++, veja abaixo outras sugestões.


Outras soluções: se você estiver usando ferramentas não compatíveis com C ++ 11

Para C99, em C, isso é implementado como uma macro isnan(c)que retorna um valor int. O tipo de xdeve ser float, double ou long double.

Vários fornecedores podem incluir ou não uma função isnan().

A maneira supostamente portátil de verificar NaNé usar a propriedade IEEE 754 que NaNnão é igual a si mesma: ou seja x == x, será falsa por xser NaN.

No entanto, a última opção pode não funcionar com todos os compiladores e algumas configurações (particularmente as configurações de otimização); portanto, em último recurso, você sempre pode verificar o padrão de bits ...

BlueTrin
fonte
8
Definitivamente merece ser a resposta aceita e merece mais votos. Obrigado pela dica
LBes 4/15
3
-1 std::isnan ainda é uma recomendação ruim a partir de fevereiro de 2017, pois não funciona com a otimização de ponto flutuante do g ++.
Saúde e hth. #
@ Cheersandhth.-Alf: esta opção é compatível com IEEE? A resposta foi editada
BlueTrin
@BlueTrin: Ambos x != xe isnansão obrigados a trabalhar para conformidade com a IEEE 754. Em relação a este último, a norma IEEE 754-2008 declara que “As implementações devem fornecer as seguintes operações não computacionais para todos os formatos aritméticos suportados” e “isNaN (x) é verdadeiro se e somente se x for um NaN”. Para verificar a conformidade exigida pelo padrão is754version1985()e is754version2008(), onde o C ++ oferece std::numeric_limits<Fp>::is_iec559()(o IEC 559 é o mesmo padrão). Infelizmente com a -ffast-mathotimização, por exemplo, o g ++ reivindica conformidade, mas não está em conformidade.
Saúde e hth. - Alf
11
Aviso: isnan (x) não funciona com a opção -ffinite-math-only no gcc e no clang
A Fog
82

Há também uma biblioteca somente de cabeçalho presente no Boost que possui ferramentas legais para lidar com tipos de dados de ponto flutuante

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

Você obtém as seguintes funções:

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);

Se você tiver tempo, consulte o kit de ferramentas de matemática completo do Boost, ele tem muitas ferramentas úteis e está crescendo rapidamente.

Além disso, ao lidar com pontos flutuantes e não flutuantes, pode ser uma boa ideia examinar as conversões numéricas .

Anônimo
fonte
11
Obrigado! É mesmo o que eu procurava.
Dr. Watson
foi adicionado no Boost 1.35 (Acabei de descobrir que meu programa não compila na antiga distribuição Linux).
Marcin
2
se você compilar com a opção - fast-math, essa função não funcionará conforme o esperado.
Gaetano Mendola
43

Existem três formas "oficiais": posix isnanmacro , c ++ 0x isnanmodelo de função , ou Visual C ++ _isnanfunção .

Infelizmente, é pouco prático detectar qual deles usar.

E, infelizmente, não há maneira confiável de detectar se você possui representação IEEE 754 com NaNs. A biblioteca padrão oferece uma maneira oficial ( numeric_limits<double>::is_iec559). Mas, na prática, compiladores como o g ++ estragam tudo.

Em teoria, pode-se usar simplesmente x != x, mas compiladores como g ++ e visual c ++ estragam tudo.

Portanto, no final, teste os padrões de bit NaN específicos , assumindo (e esperançosamente reforçando, em algum momento!) Uma representação específica, como a IEEE 754.


EDIT : como um exemplo de "compiladores como o g ++… estrague tudo", considere

#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 );
}

Compilando com g ++ (TDM-2 mingw32) 4.4.1:

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

C: \ test> gnuc x.cpp

C: \ test> a && echo funciona ... || eco! falhou
trabalho...

C: \ test> gnuc x.cpp --fast-math

C: \ test> a && echo funciona ... || eco! falhou
Falha na asserção: a! = B, arquivo x.cpp, linha 6

Este aplicativo solicitou que o Runtime o encerrasse de maneira incomum.
Entre em contato com a equipe de suporte do aplicativo para obter mais informações.
! falhou

C: \ teste> _
Felicidades e hth. - Alf
fonte
4
@ Alf: Seu exemplo funciona como esperado para mim no Mac OS X e Linux em várias versões do g ++ entre 4.0 e 4.5. A documentação para a -ffast-mathopção diz explicitamente que pode resultar em saída incorreta para programas que dependem de uma implementação exata se as regras / especificações IEEE ou ISO para funções matemáticas. Sem essa opção ativada, o uso x != xé uma maneira perfeitamente válida e portátil de testar o NaN.
Adam Rosenfield 26/03
6
@ Adam: O que você está perdendo é que o padrão C ++ não requer representação IEEE ou matemática para carros alegóricos. Tanto quanto a página do manual diz, gcc -ffast-mathainda é uma implementação em C ++ em conformidade (bem, supondo que numeric_limits::is_iec559ela esteja correta, embora Alf sugera acima que não): o código C ++ baseado no IEEE não é portátil em C ++ e não tem o direito esperar implementações para fornecê-lo.
Steve Jessop
5
E o Alf está certo, teste rápido no gcc 4.3.4 e is_iec559é verdade com -ffast-math. Portanto, o problema aqui é que os documentos do GCC -ffast-mathdizem apenas que não é IEEE / ISO para funções matemáticas, enquanto eles devem dizer que não é C ++, porque sua implementação numeric_limitsé borked. Eu acho que o GCC nem sempre pode dizer no momento em que o modelo é definido, se o eventual back-end realmente possui flutuadores conformes e, portanto, nem tenta. No IIRC, há problemas semelhantes na lista de erros pendentes para conformidade com o C99 do GCC.
31511 Steve Steveop
11
@ Alf, @ Steve, eu não sabia que o padrão C ++ não tem especificação sobre valores de ponto flutuante. É muito chocante para mim. Parece melhor lidar com o IEEE 754 e o NaN como uma extensão específica da plataforma em vez de padrão. Não é? E posso esperar que qualquer tipo de isnan () ou IEEE754 seja adicionado no C ++ 0x?
Eonil
3
@Eonil: C ++ 0x ainda possui, por exemplo, "A representação do valor dos tipos de ponto flutuante é definida pela implementação". C e C ++ têm como objetivo oferecer suporte a implementações em máquinas sem hardware de ponto flutuante, e os flutuadores IEEE 754 adequados podem ser um pouco mais lentos do que as alternativas razoavelmente precisas. A teoria é que você pode afirmar is_iec559se precisa do IEEE, na prática que parece não funcionar no GCC. O C ++ 0x tem uma isnanfunção, mas, como o GCC não é implementado corretamente is_iec559agora, acho que também não será no C ++ 0x, e -ffast-mathpode muito bem quebrar o seu isnan.
Steve Jessop
39

Existe um std :: isnan se o compilador suportar extensões c99, mas não tenho certeza se o mingw suporta.

Aqui está uma pequena função que deve funcionar se o seu compilador não tiver a função padrão:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}
CTT
fonte
6
por que não apenas var! = var?
1811 Brian Brian Bondy
8
Ao fazer isso, é uma chance do compilador otimizar a comparação, sempre retornando verdadeiro.
CTT
23
Não, não existe. Um compilador que faz isso está quebrado. Você também pode dizer que há uma chance de a biblioteca padrão isnanretornar o resultado errado. Tecnicamente verdade, o compilador pode ser buggy, mas na prática, Not Gonna Happen. O mesmo que var != var. Funciona porque é assim que os valores de ponto flutuante IEEE são definidos.
jalf
29
se -ffast-math estiver definido, isnan () falhará em retornar o resultado correto para o gcc. Claro, essa otimização está documentado como quebrar semântica IEEE ...
Matthew Herrmann
Se -ffast-math estiver definido, o compilador está com erros. Ou melhor, se -ffast-math estiver definido, todas as apostas estão desativadas e você não pode confiar nos NaNs.
Adrian Ratnapala
25

Você pode usar numeric_limits<float>::quiet_NaN( )definido na limitsbiblioteca padrão para testar. Há uma 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;
}

Não sei se isso funciona em todas as plataformas, pois testei apenas com o g ++ no Linux.

Bill the Lizard
fonte
2
Cuidado, porém - parece haver um erro em numeric_limits na versão 3.2.3 do GCC, pois ele retorna 0.0 para quiet_NaN. Versões posteriores do GCC estão bem na minha experiência.
21720 Nathan Kitchen
@ Nathan: É bom saber. Estou usando a versão 4.3.2, por isso estou fora de perigo.
Bill o Lagarto
18

Você pode usar a isnan()função, mas precisa incluir a biblioteca de matemática C.

#include <cmath>

Como essa função faz parte do C99, ela não está disponível em todos os lugares. Se o fornecedor não fornecer a função, você também poderá definir sua própria variante para compatibilidade.

inline bool isnan(double x) {
    return x != x;
}
raimue
fonte
Eu estava usando <cmath> e não há isnan nele! aliás eu descobri que não é um isnanem <math.h>
Hasen
11
Como eu disse, isso faz parte do C99. Como o C99 não faz parte de nenhum padrão C ++ atual, forneci a alternativa. Mas como é provável que isnan () seja incluído em um próximo padrão C ++, coloquei uma diretiva #ifndef em torno dele.
raimue 21/02/09
12

O código a seguir usa a definição de NAN (todos os bits de expoente definidos, pelo menos um conjunto de bits fracionários) e assume que sizeof (int) = sizeof (float) = 4. Você pode procurar NAN na Wikipedia para obter detalhes.

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

Ian
fonte
Eu acredito que isso também funcionaria em grandes plataformas endian. O literal 0x7fffffffsimplesmente ficaria na memória como ff ff ff 7f. valuetem a mesma ordem de ordem 0x7f800000, portanto, todas as operações estão alinhadas (não há troca de bytes). Eu estaria interessado se alguém pudesse testar isso em uma grande plataforma endian.
Bryan W. Wagner
0x7fff1234também é um NaN. Assim é0xffffffff
Steve Hollasch
12

prevenção nan

Minha resposta a esta pergunta é não usar verificações retroativasnan . Use verificações preventivas para divisões do formulário 0.0/0.0.

#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 da operação 0.f/0.f, ou 0.0/0.0. nané um terrível inimigo da estabilidade do seu código que deve ser detectado e evitado com muito cuidado 1 . As propriedades nandisso são diferentes dos números normais:

  • nané tóxico, (5 * nan= nan)
  • nannão é igual a nada, nem a si mesmo ( nan! = nan)
  • nannão maior que qualquer coisa ( nan!> 0)
  • nannão é menos do que qualquer coisa ( nan! <0)

As duas últimas propriedades listadas são contra-lógicas e resultarão em um comportamento ímpar do código que depende de comparações com um nannúmero (a terceira última propriedade também é ímpar, mas você provavelmente nunca verá o x != x ?seu código (a menos que esteja verificando para nan (não confiável))).

No meu próprio código, notei que os nanvalores tendem a produzir bugs difíceis de encontrar. (Observe como esse não é o caso para infou -inf. ( -inf<0) retorna TRUE, (0 < inf) retorna VERDADEIRO e até ( -inf< inf) retorna VERDADEIRO. Portanto, na minha experiência, o comportamento do código geralmente permanece como desejado).

o que fazer sob nan

O que você deseja que aconteça 0.0/0.0 deve ser tratado como um caso especial , mas o que você faz deve depender dos números que você espera que saiam do código.

No exemplo acima, o resultado de ( 0.f/FLT_MIN) será 0basicamente. Você pode 0.0/0.0gerar em seu HUGElugar. Assim,

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.

Então, acima, se x fosse 0.f,inf resultaria (que tem um comportamento muito bom / não destrutivo, como mencionado acima, na verdade).

Lembre-se, a divisão inteira por 0 causa uma exceção de tempo de execução . Portanto, você deve sempre verificar a divisão inteira por 0. Só porque 0.0/0.0avaliar discretamente nannão significa que você pode ser preguiçoso e não procurar 0.0/0.0antes que isso aconteça.

1 Às vezes, as verificações de nanvia não x != xsão confiáveis ​​( x != xsendo eliminadas por alguns compiladores de otimização que quebram a conformidade com a IEEE, especificamente quando o -ffast-mathcomutador está ativado).

bobobobo
fonte
Obrigado por apontar isso; uma programação como essa definitivamente ajudaria no problema em si. Mas da próxima vez, tente não abusar muito dos recursos de formatação de texto. Mudar o tamanho da fonte, o peso e o estilo como esse dificulta a leitura.
Magnus
4
Observe que 0,0 / 0,0 não é a única operação que pode resultar em um NaN. A raiz quadrada de um número negativo retorna NaN. O cosseno de + infinito também retorna NaN. a operação acos (x) onde x não está no intervalo [0, pi] também pode resultar em NaN. Em poucas palavras, é preciso ter um cuidado extra para observar também essas operações potencialmente arriscadas, não apenas para 0,0 / 0,0.
Boris Dalstein 28/10
Concordo totalmente com Boris. Na minha experiência, o NaN praticamente sempre veio de algo como sqrt (-1.302e-53), ou seja, resultados de computação intermediária próximos de zero sendo alimentados no sqrt sem verificar a negatividade.
hans_meine
11
"Prevenir NaNs" significa que você precisa entrar em todas as operações aritméticas básicas, não apenas na divisão. Você precisará prestar atenção a ∞ / ∞, 0 * ∞,% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0, entre muitos outros. Ser "preventivo" com essas operações aritméticas básicas significa que você reduzirá completamente seu desempenho (e provavelmente perderá casos adicionais em que não pensou).
Steve Hollasch
11

No C ++ 14, existem várias maneiras de testar se um número de ponto flutuante value é um NaN.

Dessas formas, apenas a verificação dos bits da representação do número funciona de maneira confiável, conforme observado na minha resposta original. Em particular, std::isnane a verificação frequentemente propostav != v , não funciona de maneira confiável e não deve ser usada, para que seu código pare de funcionar corretamente quando alguém decide que a otimização de ponto flutuante é necessária e solicita que o compilador faça isso. Essa situação pode mudar, os compiladores podem ficar mais conformes, mas para esse problema que não aconteceu nos 6 anos desde a resposta original.

Por cerca de 6 anos, minha resposta original foi a solução selecionada para esta pergunta, o que foi bom. Mas, recentemente, uma resposta altamente votada, recomendando o v != vteste não confiável , foi selecionada. Portanto, essa resposta adicional mais atualizada (agora temos os padrões C ++ 11 e C ++ 14 e C ++ 17 no horizonte).


As principais maneiras de verificar o NaN-ness, a partir do C ++ 14, são:

  • std::isnan(value) )
    é a maneira de biblioteca padrão pretendida desde C ++ 11. isnanaparentemente entra em conflito com a macro Posix de mesmo nome, mas na prática isso não é um problema. O principal problema é que, quando a otimização aritmética de ponto flutuante é solicitada, pelo menos um compilador principal, ou seja, g ++, std::isnan retorna falsepara o argumento NaN .

  • (fpclassify(value) == FP_NAN) )
    Sofre do mesmo problema que std::isnan, ou seja, não é confiável.

  • (value != value) )
    Recomendado em muitas respostas SO. Sofre do mesmo problema que std::isnan, ou seja, não é confiável.

  • (value == Fp_info::quiet_NaN()) )
    Este é um teste que, com o comportamento padrão, não deve detectar NaNs, mas que, com o comportamento otimizado, pode detectar NaNs (devido ao código otimizado apenas comparando diretamente as representações de nível de bit) e talvez combinado com outra maneira de cobrir o comportamento não otimizado padrão , pode detectar de forma confiável o NaN. Infelizmente, acabou por não funcionar de forma confiável.

  • (ilogb(value) == FP_ILOGBNAN) )
    Sofre do mesmo problema que std::isnan, ou seja, não é confiável.

  • isunordered(1.2345, value) )
    Sofre do mesmo problema que std::isnan, ou seja, não é confiável.

  • is_ieee754_nan( value ) )
    Esta não é uma função padrão. É a verificação dos bits de acordo com o padrão IEEE 754. É completamente confiável mas o código depende um pouco do sistema.


No código de teste completo a seguir, “success” é se uma expressão relata Nan-ness do valor. Para a maioria das expressões, essa medida de sucesso, o objetivo de detectar NaNs e apenas NaNs, corresponde à sua semântica padrão. Para a (value == Fp_info::quiet_NaN()) )expressão, no entanto, o comportamento padrão é que ele não funciona como um 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 com g ++ (observe novamente que o comportamento padrão de (value == Fp_info::quiet_NaN())é que ele não funciona como um detector de NaN, é apenas um interesse prático aqui):

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> g ++ --versão | encontre "++"
g ++ (x86_64-win32-sjlj-rev1, criado pelo projeto MinGW-W64) 6.3.0

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> g ++ foo.cpp && a
O compilador reivindica IEEE 754 = true

v = nan, (std :: isnan (valor)) = verdadeiro Sucesso
u = 3.14, (std :: isnan (valor)) = false Sucesso
w = inf, (std :: isnan (valor)) = false Sucesso

v = nan, ((fpclassify (value) == 0x0100)) = verdadeiro sucesso
u = 3.14, ((fpclassify (valor) == 0x0100)) = false Sucesso
w = inf, ((fpclassify (value) == 0x0100)) = false Sucesso

v = nan, ((valor! = valor)) = verdadeiro sucesso
u = 3,14, ((valor! = valor)) = falso Sucesso
w = inf, ((valor! = valor)) = falso Sucesso

v = nan, ((valor == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = false Sucesso
w = inf, ((valor == Fp_info :: quiet_NaN ())) = false Sucesso

v = nan, ((ilogb (value) == ((int) 0x80000000))) = verdadeiro sucesso
u = 3.14, ((ilogb (valor) == ((int) 0x80000000)))) = sucesso falso
w = inf, ((ilogb (value) == ((int) 0x80000000))) = false Sucesso

v = nan, (isunordered (1.2345, value)) = verdadeiro Sucesso
u = 3.14, (sem ordem (1.2345, valor)) = false Sucesso
w = inf, (isunordered (1.2345, value)) = false Sucesso

v = nan, (is_ieee754_nan (valor)) = verdadeiro Sucesso
u = 3.14, (is_ieee754_nan (valor)) = false Sucesso
w = inf, (is_ieee754_nan (value)) = false Sucesso

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> g ++ foo.cpp -ffast-math && a
O compilador reivindica IEEE 754 = true

v = nan, (std :: isnan (valor)) = falso FAILED
u = 3.14, (std :: isnan (valor)) = false Sucesso
w = inf, (std :: isnan (valor)) = false Sucesso

v = nan, ((fpclassify (valor) == 0x0100)) = falso FAILED
u = 3.14, ((fpclassify (valor) == 0x0100)) = false Sucesso
w = inf, ((fpclassify (value) == 0x0100)) = false Sucesso

v = nan, ((valor! = valor)) = falso FAILED
u = 3,14, ((valor! = valor)) = falso Sucesso
w = inf, ((valor! = valor)) = falso Sucesso

v = nan, ((valor == Fp_info :: quiet_NaN ())) = verdadeiro Sucesso
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = verdadeiro FAILED
w = inf, ((valor == Fp_info :: quiet_NaN ())) = verdadeiro FAILED

v = nan, ((ilogb (value) == ((int) 0x80000000))) = verdadeiro sucesso
u = 3.14, ((ilogb (valor) == ((int) 0x80000000)))) = sucesso falso
w = inf, ((ilogb (value) == ((int) 0x80000000))) = false Sucesso

v = nan, (isunordered (1.2345, value)) = false FAILED
u = 3.14, (sem ordem (1.2345, valor)) = false Sucesso
w = inf, (isunordered (1.2345, value)) = false Sucesso

v = nan, (is_ieee754_nan (valor)) = verdadeiro Sucesso
u = 3.14, (is_ieee754_nan (valor)) = false Sucesso
w = inf, (is_ieee754_nan (value)) = false Sucesso

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> _

Resultados com o Visual C ++:

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> cl / nologo- 2> & 1 | encontre "++"
Microsoft (R) C / C ++ Optimizing Compiler versão 19.00.23725 para x86

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> cl foo.cpp / fev &&b
foo.cpp
O compilador reivindica IEEE 754 = true

v = nan, (std :: isnan (valor)) = verdadeiro Sucesso
u = 3.14, (std :: isnan (valor)) = false Sucesso
w = inf, (std :: isnan (valor)) = false Sucesso

v = nan, ((fpclassify (value) == 2)) = verdadeiro sucesso
u = 3,14, ((fpclassify (valor) == 2)) = falso Sucesso
w = inf, ((fpclassify (value) == 2)) = false Sucesso

v = nan, ((valor! = valor)) = verdadeiro sucesso
u = 3,14, ((valor! = valor)) = falso Sucesso
w = inf, ((valor! = valor)) = falso Sucesso

v = nan, ((valor == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = false Sucesso
w = inf, ((valor == Fp_info :: quiet_NaN ())) = false Sucesso

v = nan, ((ilogb (valor) == 0x7fffffff)) = verdadeiro sucesso
u = 3.14, ((ilogb (valor) == 0x7fffffff)) = falso Sucesso
w = inf, ((ilogb (valor) == 0x7fffffff)) = verdadeiro FALHOU

v = nan, (isunordered (1.2345, value)) = verdadeiro Sucesso
u = 3.14, (sem ordem (1.2345, valor)) = false Sucesso
w = inf, (isunordered (1.2345, value)) = false Sucesso

v = nan, (is_ieee754_nan (valor)) = verdadeiro Sucesso
u = 3.14, (is_ieee754_nan (valor)) = false Sucesso
w = inf, (is_ieee754_nan (value)) = false Sucesso

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> cl foo.cpp / Feb / fp: fast && b
foo.cpp
O compilador reivindica IEEE 754 = true

v = nan, (std :: isnan (valor)) = verdadeiro Sucesso
u = 3.14, (std :: isnan (valor)) = false Sucesso
w = inf, (std :: isnan (valor)) = false Sucesso

v = nan, ((fpclassify (value) == 2)) = verdadeiro sucesso
u = 3,14, ((fpclassify (valor) == 2)) = falso Sucesso
w = inf, ((fpclassify (value) == 2)) = false Sucesso

v = nan, ((valor! = valor)) = verdadeiro sucesso
u = 3,14, ((valor! = valor)) = falso Sucesso
w = inf, ((valor! = valor)) = falso Sucesso

v = nan, ((valor == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((valor == Fp_info :: quiet_NaN ())) = false Sucesso
w = inf, ((valor == Fp_info :: quiet_NaN ())) = false Sucesso

v = nan, ((ilogb (valor) == 0x7fffffff)) = verdadeiro sucesso
u = 3.14, ((ilogb (valor) == 0x7fffffff)) = falso Sucesso
w = inf, ((ilogb (valor) == 0x7fffffff)) = verdadeiro FALHOU

v = nan, (isunordered (1.2345, value)) = verdadeiro Sucesso
u = 3.14, (sem ordem (1.2345, valor)) = false Sucesso
w = inf, (isunordered (1.2345, value)) = false Sucesso

v = nan, (is_ieee754_nan (valor)) = verdadeiro Sucesso
u = 3.14, (is_ieee754_nan (valor)) = false Sucesso
w = inf, (is_ieee754_nan (value)) = false Sucesso

[C: \ meus \ fóruns \ so \ 282 (detectar NaN)]
> _

Resumindo os resultados acima, apenas o teste direto da representação no nível de bit, usando a is_ieee754_nanfunção definida neste programa de teste, funcionou de maneira confiável em todos os casos com o g ++ e o Visual C ++.


Adendo:
Depois de postar o texto acima, tomei conhecimento de mais um possível teste para NaN, mencionado em outra resposta aqui, a saber ((value < 0) == (value >= 0)). Isso acabou funcionando bem com o Visual C ++, mas falhou com a -ffast-mathopção do g ++ . Somente testes diretos de bitpattern funcionam de maneira confiável.

Felicidades e hth. - Alf
fonte
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);
}

Isso funciona se sizeof(int)for 4 e sizeof(long long)for 8.

Durante o tempo de execução, é apenas uma comparação, as peças vazadas não levam tempo. Apenas altera a configuração dos sinalizadores de comparação para verificar a igualdade.

ST3
fonte
Observe também que está limitado à representação IEEE 754.
Saúde e hth. #
Observe que essa conversão quebra a regra estrita de aliasing do g ++, e esse compilador é conhecido por fazer Unmentionable Things ™ quando detecta UB formal. Em vez de lançamentos eficientes, com o g ++ você precisa usar memcpy, através de uma matriz de bytes, para ter certeza. Código para isso na minha resposta # 2 .
Saúde e hth. - Alf
4

Uma possível solução que não dependeria da representação IEEE específica para NaN usada seria a seguinte:

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
fonte
O ponto flutuante de precisão única possui mais de 8 milhões de representações de bits legítimas e diferentes para o NaN, portanto, você precisará adicionar mais algumas comparações. :)
Steve Hollasch
4

Considerando que (x! = X) nem sempre é garantido para NaN (como se estivesse usando a opção -ffast-math), eu tenho usado:

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

Os números não podem ser ambos <0 e> = 0; portanto, essa verificação só passa se o número não for menor que, nem maior que ou igual a zero. O que é basicamente nenhum número, ou NaN.

Você também pode usar isso se preferir:

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

Não tenho certeza de como isso é afetado pela matemática -fast, portanto, sua milhagem pode variar.

Jerramy
fonte
Isso é realmente defeituoso da mesma maneira que também f != fé defeituoso. Eu vi o llvm otimizando um código quase idêntico. O otimizador pode propagar as informações sobre a primeira comparação e descobrir que a segunda comparação poderá nunca ser verdadeira se a primeira for. (se o compilador estritamente obedece regras IEEE f != fé muito mais simples de qualquer maneira)
Markus
Não funciona com a -ffast-mathopção do g ++ . Funciona com o Visual C ++. Consulte ( stackoverflow.com/a/42138465/464581 ).
Saúde e hth. #
3

Quanto a mim, a solução poderia ser uma macro para torná-la explicitamente integrada e, portanto, rápida o suficiente. Também funciona para qualquer tipo de flutuador. Baseia-se no fato de que o único caso em que um valor não é igual é quando o valor não é um número.

#ifndef isnan
  #define isnan(a) (a != a)
#endif
user1705817
fonte
Esta é uma das melhores respostas para esta pergunta! Obrigado por compartilhar.
Henri Menke
2
Outras respostas indicam que isso pode falhar com o conjunto de opções -ffast-math.
Technophile
3

Isso 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;
}

saída: isnan

edW
fonte
1

Parece-me que a melhor abordagem verdadeiramente multiplataforma seria usar uma união e testar o padrão de bits da dupla para verificar NaNs.

Não testei completamente essa solução e pode haver uma maneira mais eficiente de trabalhar com os padrões de bits, mas acho que ela deve 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
fonte
Observe que "é um comportamento indefinido ler do membro do sindicato que não foi escrito mais recentemente". Portanto, esse uso de um uniontipo-trocadilho entre dois tipos pode não funcionar como desejado (: sad_panda :). A maneira correta (embora não tão portátil quanto desejada) seria evitar a união completamente e fazer um memcpy da doubleem uma uint64_tvariável diferente e , em seguida, fazer o teste usando essa variável auxiliar.
Eljay
0

No x86-64, você pode ter métodos extremamente rápidos para verificar o NaN e o infinito, que funcionam independentemente da -ffast-mathopção do compilador. ( f != f, std::isnan, std::isinfSempre produzir falsecom -ffast-math).


Testes para NaN, infinito e números finitos podem ser feitos facilmente, verificando-se o máximo de expoente. infinito é expoente máximo com mantissa zero, NaN é expoente máximo e mantissa diferente de zero. O expoente é armazenado nos próximos bits após o bit de sinal mais alto, de modo que podemos apenas sair à esquerda para se livrar do bit de sinal e tornar o expoente os bits mais altos, sem mascaramento ( operator&):

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

As stdversões isinfe isfinitecarregam 2 double/floatconstantes do .datasegmento e, na pior das hipóteses, podem causar 2 falhas no cache de dados. As versões acima não carregam dados inf_double_shl1e as inf_float_shl1constantes são codificadas como operandos imediatos nas instruções de montagem.


Mais rápido isnan2é apenas 2 instruções de montagem:

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

Usa o fato de que a ucomisdinstrução define sinalizador de paridade se algum argumento for NaN. É assim que std::isnanfunciona quando nenhuma -ffast-mathopção é especificada.

Maxim Egorushkin
fonte
-1

O padrão IEEE diz que quando o expoente é todo se 1a mantissa não é zero, o número é a NaN. Duplo é 1bit de sinal, 11bits de expoente e 52bits de mantissa. Faça uma pequena verificação.

bop
fonte
-3

Como os comentários acima afirmam, um! = A não funcionará em g ++ e em alguns outros compiladores, mas esse truque deve. Pode não ser tão eficiente, mas ainda é uma maneira:

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

Basicamente, em g ++ (não tenho certeza de outros), printf imprime 'nan' nos formatos% d ou% .f, se a variável não for um número inteiro / float válido. Portanto, esse código está verificando se o primeiro caractere da string é 'n' (como em "nan")

ZenJ
fonte
2
Isso não causaria um estouro de buffer se a = 234324.0f?
Mazyod
Sim, ou 340282346638528859811704183484516925440.000se a = FLT_MAX. Ele teria que usar char s[7]; sprintf(s, "%.0g", a);, que será de 6 chrs se a=-FLT_MAX, ou-3e+38
bobobobo
-3

Isso detecta o infinito e também o NaN no Visual Studio, verificando se está dentro de limites duplos:

//#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
fonte
Verifique a definição de FLT_MIN, DBL_MINe LDBL_MINcom mais cuidado. Estes são definidos como os menores valores normalizados para cada tipo. Por exemplo, a precisão única possui mais de 8 milhões de valores de denorm legítimos maiores que zero e menores que FLT_MIN(e não são NaN).
Steve Hollasch