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.
É 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.
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
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 ...
Definitivamente merece ser a resposta aceita e merece mais votos. Obrigado pela dica
LBes 4/15
3
-1std::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
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;doubleconst nan1 =Info::quiet_NaN();doubleconst 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> _
@ 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){volatiledouble d = var;return d != d;}
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>usingnamespace 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;}return0;}
Não sei se isso funciona em todas as plataformas, pois testei apenas com o g ++ no Linux.
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.
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.
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.0deve 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 possibleelse
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).
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::isnanretorna 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.
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.
inlineboolIsNan(float f){constuint32 u =*(uint32*)&f;return(u&0x7F800000)==0x7F800000&&(u&0x7FFFFF);// Both NaN and qNan.}inlineboolIsNan(double d){constuint64 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.
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;return0== memcmp((void*)&f,(void*)&_nan,sizeof(T));}
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.
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)
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.
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>unionNaN{uint64_t bits;double num;};int main(){//Test if a double is NaNdouble d =0.0/0.0;unionNaN n;
n.num = d;if((n.bits |0x800FFFFFFFFFFFFF)==0xFFFFFFFFFFFFFFFF){
printf("NaN: %f", d);}return0;}
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&):
staticinlineuint64_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;}staticinlineuint32_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;}constexpruint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);constexpruint32_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.staticinlinebool isnan2(double a){return load_ieee754_rep(a)<<1> inf_double_shl1;}staticinlinebool isinf2(double a){return load_ieee754_rep(a)<<1== inf_double_shl1;}staticinlinebool isfinite2(double a){return load_ieee754_rep(a)<<1< inf_double_shl1;}staticinlinebool isnan2(float a){return load_ieee754_rep(a)<<1> inf_float_shl1;}staticinlinebool isinf2(float a){return load_ieee754_rep(a)<<1== inf_float_shl1;}staticinlinebool 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:
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.
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.
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:
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")
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;
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).
nan
s no seu código.nan
Isso pode ser terrivelmente destrutivo para o seu programa; se for permitido proliferar, pode ser introduzido bugs difíceis de encontrar. Isso ocorre porquenan
é tóxico (5 *nan
=nan
),nan
não é igual a nada (nan
! =nan
),nan
Não é maior que nada (nan
!> 0),nan
não é menor que qualquer coisa (nan
! <0).Respostas:
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 != f
será 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.
fonte
-ffast-math
opçã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 usox != x
é uma maneira perfeitamente válida e portátil de testar o NaN.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.-ffast-math
opção),x != x
será uma solução válida e portátil. Você pode até testar-ffast-math
testando a__FAST_MATH__
macro e alternar para uma implementação diferente nesse caso (por exemplo, use uniões e ajuste de bits).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
cmath
incluirá elementos C99 comoisnan()
,isfinite()
etc., mas eles serão definidos como funções, não macros, geralmente nostd::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 parastd::
, portanto,std::isnan
está bem definido.Além disso, algumas implementações de C ++ ainda disponibilizam a
isnan()
macro C99 para C ++ (incluída porcmath
oumath.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::isnan
nenhumstd::tr1::isnan
, mas fornece uma função de extensão definida como_isnan()
disponível desde o Visual C ++ 6.0No 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.fonte
std::isnan
agora 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 :-)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 ++ 11Sinopse
Definido no cabeçalho
<cmath>
Determina se o número de ponto flutuante arg especificado não é um número (
NaN
).Parâmetros
arg
: valor do ponto flutuanteValor de retorno
true
se arg forNaN
,false
caso contrárioReferê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 dex
deve 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 queNaN
não é igual a si mesma: ou sejax == x
, será falsa porx
serNaN
.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 ...
fonte
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 ++.x != x
eisnan
sã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ãois754version1985()
eis754version2008()
, onde o C ++ oferecestd::numeric_limits<Fp>::is_iec559()
(o IEC 559 é o mesmo padrão). Infelizmente com a-ffast-math
otimização, por exemplo, o g ++ reivindica conformidade, mas não está em conformidade.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
Você obtém as seguintes funções:
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 .
fonte
Existem três formas "oficiais": posix
isnan
macro , c ++ 0xisnan
modelo de função , ou Visual C ++_isnan
funçã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
Compilando com g ++ (TDM-2 mingw32) 4.4.1:
fonte
-ffast-math
opçã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 usox != x
é uma maneira perfeitamente válida e portátil de testar o NaN.gcc -ffast-math
ainda é uma implementação em C ++ em conformidade (bem, supondo quenumeric_limits::is_iec559
ela 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.is_iec559
é verdade com-ffast-math
. Portanto, o problema aqui é que os documentos do GCC-ffast-math
dizem apenas que não é IEEE / ISO para funções matemáticas, enquanto eles devem dizer que não é C ++, porque sua implementaçãonumeric_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.is_iec559
se precisa do IEEE, na prática que parece não funcionar no GCC. O C ++ 0x tem umaisnan
função, mas, como o GCC não é implementado corretamenteis_iec559
agora, acho que também não será no C ++ 0x, e-ffast-math
pode muito bem quebrar o seuisnan
.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:
fonte
isnan
retornar o resultado errado. Tecnicamente verdade, o compilador pode ser buggy, mas na prática, Not Gonna Happen. O mesmo quevar != var
. Funciona porque é assim que os valores de ponto flutuante IEEE são definidos.Você pode usar
numeric_limits<float>::quiet_NaN( )
definido nalimits
biblioteca padrão para testar. Há uma constante separada definida paradouble
.Não sei se isso funciona em todas as plataformas, pois testei apenas com o g ++ no Linux.
fonte
Você pode usar a
isnan()
função, mas precisa incluir a biblioteca de matemática C.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.
fonte
isnan
em <math.h>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; }
fonte
0x7fffffff
simplesmente ficaria na memória comoff ff ff 7f
.value
tem a mesma ordem de ordem0x7f800000
, 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.0x7fff1234
também é um NaN. Assim é0xffffffff
prevenção nan
Minha resposta a esta pergunta é não usar verificações retroativas
nan
. Use verificações preventivas para divisões do formulário0.0/0.0
.nan
resultados da operação0.f/0.f
, ou0.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 propriedadesnan
disso são diferentes dos números normais:nan
é tóxico, (5 *nan
=nan
)nan
não é igual a nada, nem a si mesmo (nan
! =nan
)nan
não maior que qualquer coisa (nan
!> 0)nan
nã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
nan
número (a terceira última propriedade também é ímpar, mas você provavelmente nunca verá ox != x ?
seu código (a menos que esteja verificando para nan (não confiável))).No meu próprio código, notei que os
nan
valores tendem a produzir bugs difíceis de encontrar. (Observe como esse não é o caso parainf
ou-inf
. (-inf
<0) retornaTRUE
, (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á0
basicamente. Você pode0.0/0.0
gerar em seuHUGE
lugar. Assim,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.0
avaliar discretamentenan
não significa que você pode ser preguiçoso e não procurar0.0/0.0
antes que isso aconteça.1 Às vezes, as verificações de
nan
via nãox != x
são confiáveis (x != x
sendo eliminadas por alguns compiladores de otimização que quebram a conformidade com a IEEE, especificamente quando o-ffast-math
comutador está ativado).fonte
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::isnan
e 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 != v
teste 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.
isnan
aparentemente 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
retornafalse
para 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.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):Resultados com o Visual C ++:
Resumindo os resultados acima, apenas o teste direto da representação no nível de bit, usando a
is_ieee754_nan
funçã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-math
opção do g ++ . Somente testes diretos de bitpattern funcionam de maneira confiável.fonte
Isso funciona se
sizeof(int)
for 4 esizeof(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.
fonte
memcpy
, através de uma matriz de bytes, para ter certeza. Código para isso na minha resposta # 2 .Uma possível solução que não dependeria da representação IEEE específica para NaN usada seria a seguinte:
fonte
Considerando que (x! = X) nem sempre é garantido para NaN (como se estivesse usando a opção -ffast-math), eu tenho usado:
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:
Não tenho certeza de como isso é afetado pela matemática -fast, portanto, sua milhagem pode variar.
fonte
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 IEEEf != f
é muito mais simples de qualquer maneira)-ffast-math
opção do g ++ . Funciona com o Visual C ++. Consulte ( stackoverflow.com/a/42138465/464581 ).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.
fonte
Isso funciona:
saída: isnan
fonte
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.
fonte
union
tipo-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 dadouble
em umauint64_t
variável diferente e , em seguida, fazer o teste usando essa variável auxiliar.No x86-64, você pode ter métodos extremamente rápidos para verificar o NaN e o infinito, que funcionam independentemente da
-ffast-math
opção do compilador. (f != f
,std::isnan
,std::isinf
Sempre produzirfalse
com-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&
):As
std
versõesisinf
eisfinite
carregam 2double/float
constantes do.data
segmento e, na pior das hipóteses, podem causar 2 falhas no cache de dados. As versões acima não carregam dadosinf_double_shl1
e asinf_float_shl1
constantes são codificadas como operandos imediatos nas instruções de montagem.Mais rápido
isnan2
é apenas 2 instruções de montagem:Usa o fato de que a
ucomisd
instrução define sinalizador de paridade se algum argumento for NaN. É assim questd::isnan
funciona quando nenhuma-ffast-math
opção é especificada.fonte
O padrão IEEE diz que quando o expoente é todo se
1
a mantissa não é zero, o número é aNaN
. Duplo é1
bit de sinal,11
bits de expoente e52
bits de mantissa. Faça uma pequena verificação.fonte
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:
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")
fonte
340282346638528859811704183484516925440.000
se a =FLT_MAX
. Ele teria que usarchar s[7]; sprintf(s, "%.0g", a);
, que será de 6 chrs sea=-FLT_MAX
, ou-3e+38
Isso detecta o infinito e também o NaN no Visual Studio, verificando se está dentro de limites duplos:
fonte
FLT_MIN
,DBL_MIN
eLDBL_MIN
com 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 queFLT_MIN
(e não são NaN).