Como usar nan e inf em C?

91

Eu tenho um método numérico que poderia retornar nan ou inf se houvesse um erro e, para fins de teste, gostaria de forçá-lo temporariamente a retornar nan ou inf para garantir que a situação esteja sendo tratada corretamente. Existe uma maneira confiável e independente do compilador para criar valores de nan e inf em C?

Depois de pesquisar no Google por cerca de 10 minutos, só consegui encontrar soluções dependentes do compilador.

Graphics Noob
fonte
flutuadores não são definidos pelo padrão C. Portanto, não existe uma maneira independente do compilador de fazer o que você deseja.
Johan Kotlinski

Respostas:

87

Você pode testar se sua implementação tem:

A existência de INFINITYé garantida por C99 (ou pelo menos o rascunho mais recente) e "se expande para uma expressão constante do tipo float representando infinito positivo ou sem sinal, se disponível; do contrário, para uma constante positiva do tipo float que transborda no momento da tradução".

NAN pode ou não ser definido, e "é definido se e somente se a implementação suportar NaNs silenciosos para o tipo float. Ele se expande para uma expressão constante do tipo float representando um NaN silencioso."

Observe que se você estiver comparando valores de ponto flutuante, faça:

mesmo assim,

é falso. Uma maneira de verificar o NaN seria:

Você também pode fazer: a != apara testar se aé NaN.

Há também isfinite(), isinf(), isnormal(), e signbit()macros no math.hno C99.

C99 também tem nanfunções:

(Referência: n1256).

Docs INFINITY Docs NAN

Alok Singhal
fonte
2
Excelente resposta. A referência para as macros NAN e INFINITY é C99 §7.12 parágrafos 4 e 5. Além de (isnan (a)), você também pode verificar se há NaN usando (a! = A) em uma implementação em conformidade de C.
Stephen Canon
24
Por amor à legibilidade, NUNCAa != a deve ser usado.
Chris Kerekes de
1
@ChrisKerekes: infelizmente, alguns de nós têm NAN, mas não isnan (). Sim, estamos em 2017. :(
ef
C não requer, quando anão é um número, a == NANque retorne falso. IEEE requer isso. Mesmo as implementações que aderem ao IEEE, o fazem principalmente . Quando isnan()não implementado, ainda é melhor encerrar o teste do que codificar diretamente a == NAN.
chux - Reintegrar Monica
34

Não há uma maneira independente do compilador de fazer isso, pois nem os padrões C (nem os C ++) dizem que os tipos matemáticos de ponto flutuante devem oferecer suporte a NAN ou INF.

Edit: Acabei de verificar o texto do padrão C ++, e ele diz que essas funções (membros da classe modelada numeric_limits):

retornará representações NAN "se disponíveis". Não expande o que significa "se disponível", mas provavelmente algo como "se o representante de FP da implementação os apoiar". Da mesma forma, existe uma função:

que retorna um rep INF positivo "se disponível".

Ambos estão definidos no <limits>cabeçalho - acho que o padrão C tem algo semelhante (provavelmente também "se disponível"), mas não tenho uma cópia do padrão C99 atual.


fonte
Isso é decepcionante e surpreendente. C e C ++ não estão em conformidade com os números de ponto flutuante IEEE, que têm uma representação padrão para nan e inf?
Graphics Noob
14
Em C99, o cabeçalho C <math.h>define nan(), nanf()e nanl()que retornam diferentes representações de NaN (como um double, floate int, respectivamente), e o infinito (se disponível) pode ser devolvido através da geração de um com log(0)ou algo. Não há uma maneira padrão de verificá-los, mesmo em C99. O <float.h>cabeçalho ( <limits.h>é para tipos integrais) infelizmente não fala sobre os valores infe nan.
Chris Lutz
Uau, isso é uma grande confusão. nanl()retorna um long double, não um intcomo meu comentário diz. Não sei por que não percebi isso quando estava digitando.
Chris Lutz
@Chris, veja minha resposta para C99.
Alok Singhal
2
@IngeHenriksen - Tenho certeza que a Microsoft declarou que não tem nenhuma intenção de o VC ++ oferecer suporte ao C99.
Chris Lutz,
25

Isso funciona para ambos floate double:

Edit: Como alguém já disse, o antigo padrão IEEE dizia que tais valores deveriam gerar armadilhas. Mas os novos compiladores quase sempre desligam as armadilhas e retornam os valores fornecidos porque a armadilha interfere no tratamento de erros.

Thorsten S.
fonte
Trapping era uma opção para tratamento de erros permitida em 754-1985. O comportamento usado pela maioria dos hardwares / compiladores modernos também era permitido (e era o comportamento preferido por muitos dos membros do comitê). Muitos implementadores presumiram incorretamente que o trapping era necessário devido ao uso infeliz do termo "exceções" no padrão. Isso foi bastante esclarecido no documento 754-2008 revisado.
Stephen Canon
Olá, Stephen, você está certo, mas o padrão também diz: "Um usuário deve ser capaz de solicitar uma interceptação em qualquer uma das cinco exceções, especificando um manipulador para ela. Ele deve ser capaz de solicitar que um manipulador existente seja desativado , salvo ou restaurado. Ele também deve ser capaz de determinar se um manipulador de interceptação específico para uma exceção designada foi habilitado. " "deveria", conforme definido (2. Definições), significa "fortemente recomendado" e sua implementação só deve ser deixada de fora se a arquitetura etc. tornar isso impraticável. O 80x86 oferece suporte total ao padrão, portanto, não há razão para C não o suportar.
Thorsten S.
Concordo que C deve exigir ponto flutuante 754 (2008), mas há boas razões para não exigir; especificamente, C é usado em todos os tipos de ambientes diferentes de x86 - incluindo dispositivos incorporados que não têm ponto flutuante de hardware e dispositivos de processamento de sinal onde os programadores nem querem usar ponto flutuante. Certo ou errado, esses usos são responsáveis ​​por muita inércia na especificação do idioma.
Stephen Canon
Não sei por que a melhor resposta apareceu ali. Não dá como produzir os valores solicitados. Esta resposta sim.
drysdam
#define is_nan(x) ((x) != (x))pode ser útil como um teste simples e portátil para NAN.
Bob Stein
21

Uma maneira independente do compilador, mas não uma maneira independente do processador de obter estes:

Isso deve funcionar em qualquer processador que use o formato de ponto flutuante IEEE 754 (o que o x86 faz).

ATUALIZAÇÃO: Testado e atualizado.

Aaron
fonte
2
@WaffleMatt - por que não esta porta entre 32/64 bits? O float de precisão simples IEEE 754 é de 32 bits, independentemente do tamanho de endereçamento do processador subjacente.
Aaron
6
Transmitindo para (float &)? Isso não parece C para mim. Você precisaint i = 0x7F800000; return *(float *)&i;
Chris Lutz
6
Observe que 0x7f800001é uma sinalização chamada NaN no padrão IEEE-754. Embora a maioria das bibliotecas e hardware não ofereçam suporte a NaNs de sinalização, provavelmente é melhor retornar um NaN silencioso como 0x7fc00000.
Stephen Canon
6
Aviso: isso pode acionar o comportamento indefinido por meio da violação de regras estritas de aliasing . A maneira recomendada (e melhor suportada em compiladores) de fazer trocadilhos é por meio de membros do sindicato .
ulidtko
2
Além do problema de aliasing estrito que @ulidtko apontou, isso assume que o destino usa o mesmo endian para inteiros como ponto flutuante, o que definitivamente nem sempre é o caso.
sr.stobbe
16
J.Kraftcheck
fonte
4
Esta é uma solução portátil inteligente! C99 requer strtode converte NaN's e Inf's.
ulidtko
1
Não que haja uma desvantagem com essa solução; eles não são constantes. Você não pode usar esses valores para inicializar uma variável global por exemplo (ou para inicializar uma matriz).
Marc
1
@Marc. Você sempre pode ter uma função inicializadora que os chama uma vez e os define no namespace global. É uma desvantagem muito viável.
Mad Physicist
3

e

4pie0
fonte
0

Também estou surpreso que não sejam constantes de tempo de compilação. Mas suponho que você possa criar esses valores com bastante facilidade simplesmente executando uma instrução que retorne um resultado inválido. Dividindo por 0, log de 0, tan de 90, esse tipo de coisa.

Carl Smotricz
fonte
0

Eu costumo usar

ou

que funciona pelo menos em contextos IEEE 754 porque o valor duplo mais alto representável é aproximadamente 1e308. 1e309funcionaria tão bem quanto 1e99999, mas três noves é suficiente e memorável. Uma vez que este é um literal duplo (no #definecaso) ou um Infvalor real , ele permanecerá infinito mesmo se você estiver usando floats de 128 bits (“long double”).

Douglas Bagnall
fonte
1
Isso é muito perigoso, na minha opinião. Imagine como alguém migra seu código para floats de 128 bits em cerca de 20 anos (depois que seu código passa por uma evolução incrivelmente complexa, nenhum dos estágios que você foi capaz de prever hoje). De repente, o intervalo do expoente aumenta drasticamente e todos os seus 1e999literais não são mais arredondados para +Infinity. De acordo com as leis de Murphy, isso quebra um algoritmo. Pior: um programador humano executando a construção de "128 bits" provavelmente não detectará esse erro com antecedência. Ou seja, provavelmente será tarde demais quando esse erro for encontrado e reconhecido. Muito perigoso.
ulidtko
1
Claro, o pior cenário acima pode estar longe de ser realista. Mesmo assim, considere as alternativas! É melhor ficar do lado seguro.
ulidtko
2
“Em 20 anos”, heh. Vamos. Esta resposta não é que ruim.
alecov
@ulidtko Eu também não gosto disso, mas é mesmo?
Iharob Al Asimi
0

Esta é uma maneira simples de definir essas constantes, e tenho certeza de que é portátil:

Quando executo este código:

Eu recebo:

Patrick Chkoreff
fonte