Qual é a justificativa para todas as comparações que retornam false para valores NaN IEEE754?


267

Por que as comparações de valores de NaN se comportam de maneira diferente de todos os outros valores? Ou seja, todas as comparações com os operadores ==, <=,> =, <,> em que um ou ambos os valores são NaN retornam falso, ao contrário do comportamento de todos os outros valores.

Suponho que isso simplifique os cálculos numéricos de alguma forma, mas não consegui encontrar uma razão explicitamente declarada, nem mesmo nas Notas de Aula sobre o Status da IEEE 754 de Kahan, que discutem outras decisões de projeto em detalhes.

Esse comportamento desviante está causando problemas ao executar um processamento simples de dados. Por exemplo, ao classificar uma lista de registros com algum campo de valor real em um programa C, preciso escrever um código extra para manipular NaN como o elemento máximo, caso contrário, o algoritmo de classificação pode ficar confuso.

Edit: As respostas até agora argumentam que não faz sentido comparar NaNs.

Concordo, mas isso não significa que a resposta correta seja falsa, mas sim um Not-a-Boolean (NaB), que felizmente não existe.

Portanto, a escolha de retornar verdadeiro ou falso para comparações é arbitrária e, para o processamento geral de dados, seria vantajoso se obedecesse às leis usuais (reflexividade de ==, tricotomia de <, ==,>), para que estruturas de dados que dependem dessas leis ficam confusas.

Então, estou pedindo uma vantagem concreta de violar essas leis, não apenas o raciocínio filosófico.

Edit 2: Acho que agora entendo por que tornar NaN máximo seria uma má idéia, isso atrapalharia o cálculo dos limites superiores.

NaN! = NaN pode ser desejável para evitar a detecção de convergência em um loop, como

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

que, no entanto, deve ser melhor escrito comparando a diferença absoluta com um pequeno limite. Então, IMHO, esse é um argumento relativamente fraco para quebrar a reflexividade no NaN.


2
Depois que um NaN entra no cálculo, ele normalmente nunca sai, então seu teste de convergência se tornaria um loop infinito. Geralmente, é preferível relatar a falha na convergência para a rotina de chamadas, possivelmente retornando o NaN. Assim, a estrutura do loop normalmente se tornaria algo como while (fabs(x - oldX) > threshold)sair do loop se ocorrer convergência ou um NaN entrar na computação. A detecção do NaN e o remédio apropriado aconteceriam fora do circuito.
Stephen Canon

1
Se NaN fosse o elemento mínimo da ordem, o loop while ainda funcionaria.
starblue

2
Alimento para o pensamento: grouper.ieee.org/groups/1788/email/pdfmPSi1DgZZf.pdf página 10
starblue

Respostas:


535

Eu era membro do comitê IEEE-754, tentarei esclarecer um pouco as coisas.

Primeiro, números de ponto flutuante não são números reais e a aritmética de ponto flutuante não satisfaz os axiomas da aritmética real. A tricotomia não é a única propriedade da aritmética real que não vale para carros alegóricos, nem mesmo a mais importante. Por exemplo:

  • A adição não é associativa.
  • A lei distributiva não se aplica.
  • Existem números de ponto flutuante sem inversos.

Eu poderia continuar. Não é possível especificar um tipo aritmético de tamanho fixo que satisfaça todas as propriedades da aritmética real que conhecemos e amamos. O comitê 754 tem que decidir dobrar ou quebrar alguns deles. Isso é guiado por alguns princípios bastante simples:

  1. Quando podemos, combinamos o comportamento da aritmética real.
  2. Quando não podemos, tentamos tornar as violações o mais previsíveis e o mais fácil de diagnosticar possível.

Em relação ao seu comentário "que não significa que a resposta correta seja falsa", isso está errado. O predicado (y < x)pergunta se yé menor que x. Se yé NaN, então é não menos do que qualquer valor de ponto flutuante x, então a resposta é necessariamente falsa.

Mencionei que a tricotomia não se aplica a valores de ponto flutuante. No entanto, existe uma propriedade semelhante que se aplica. Cláusula 5.11, parágrafo 2 da norma 754-2008:

São possíveis quatro relações mutuamente exclusivas: menor que, igual, maior que e não ordenada. O último caso surge quando pelo menos um operando é NaN. Todo NaN deve comparar sem ordem com tudo, inclusive ele próprio.

No que diz respeito à gravação de código extra para lidar com NaNs, geralmente é possível (embora nem sempre seja fácil) estruturar seu código de forma que os NaNs sejam aprovados corretamente, mas nem sempre é esse o caso. Quando não é, algum código extra pode ser necessário, mas esse é um preço pequeno a pagar pela conveniência que o fechamento algébrico trouxe à aritmética de ponto flutuante.


Adendo: Muitos comentaristas argumentaram que seria mais útil preservar a reflexividade da igualdade e da tricotomia, alegando que a adoção de NaN! = NaN não parece preservar nenhum axioma familiar. Confesso ter simpatia por esse ponto de vista, então pensei em revisitar essa resposta e fornecer um pouco mais de contexto.

Meu entendimento ao conversar com Kahan é que NaN! = NaN se originou de duas considerações pragmáticas:

  • Isso x == ydeve ser equivalente a x - y == 0sempre que possível (além de ser um teorema da aritmética real, isso torna a implementação de comparação de hardware mais eficiente em termos de espaço, o que era de extrema importância no momento em que o padrão foi desenvolvido - observe, no entanto, que isso é violado por x = y = infinito, por isso não é uma grande razão por si só; poderia ter sido razoavelmente inclinado a (x - y == 0) or (x and y are both NaN)).

  • Mais importante, não havia isnan( )predicado na época em que o NaN foi formalizado na aritmética do 8087; era necessário fornecer aos programadores um meio conveniente e eficiente de detectar valores de NaN que não dependessem de linguagens de programação, fornecendo algo como o isnan( )que poderia levar muitos anos. Vou citar os escritos de Kahan sobre o assunto:

Se não houvesse como se livrar dos NaNs, eles seriam tão inúteis quanto os indefinidos nos CRAYs; assim que fosse encontrado, seria melhor parar o cálculo, em vez de continuar por um tempo indefinido até uma conclusão indefinida. É por isso que algumas operações com NaNs devem fornecer resultados não-NaN. Quais operações? … As exceções são os predicados C “x == x” e “x! = X”, que são respectivamente 1 e 0 para cada número infinito ou finito x, mas reversos se x não for um número (NaN); eles fornecem a única distinção simples e excepcional entre NaNs e números em idiomas que não possuem uma palavra para NaN e um predicado IsNaN (x).

Observe que essa também é a lógica que exclui o retorno de algo como um "Não-booleano". Talvez esse pragmatismo tenha sido extraviado e o padrão deveria ter sido exigido isnan( ), mas isso tornaria o NaN quase impossível de usar de maneira eficiente e conveniente por vários anos, enquanto o mundo aguardava a adoção da linguagem de programação. Não estou convencido de que teria sido uma troca razoável.

Para ser franco: o resultado de NaN == NaN não vai mudar agora. Melhor aprender a conviver com isso do que reclamar na internet. Se você quiser argumentar que também deve existir uma relação de ordem adequada para contêineres , recomendo que sua linguagem de programação favorita implemente o totalOrderpredicado padronizado em IEEE-754 (2008). O fato de ainda não falar da validade da preocupação de Kahan que motivou o estado atual das coisas.


16
Li seus pontos 1 e 2. Em seguida, observei que na aritmética real (estendida para permitir NaN em primeiro lugar) NaN é igual a si mesmo - simplesmente porque, em matemática, qualquer entidade é igual a si mesma, sem exceção. Agora estou confuso: por que o IEEE não "correspondia ao comportamento da aritmética real", o que tornaria NaN == NaN? o que estou perdendo?
Max

12
Acordado; a não-reflexividade dos NaNs não criou problemas para linguagens como Python, com sua semântica de contenção baseada em igualdade. Você realmente não deseja que a igualdade deixe de ser uma relação de equivalência quando estiver tentando criar contêineres sobre ela. E ter duas noções separadas de igualdade também não é uma opção amigável, para um idioma que é fácil de aprender. O resultado (no caso de Python) é um compromisso desagradável e frágil entre o respeito ao IEEE 754 e a semântica de contenção não muito quebrada. Felizmente, é raro colocar NaNs em recipientes.
Mark Dickinson

5
Algumas observações legais aqui: bertrandmeyer.com/2010/02/06/…
Mark Dickinson

6
@StephenCanon: De que maneira (0/0) == (+ INF) + (-INF) seria mais absurdo do que ter 1f/3f == 10000001f/30000002f? Se os valores de ponto flutuante são considerados classes de equivalência, a=bisso não significa "Os cálculos que produziram ae b, se feitos com precisão infinita, produziriam resultados idênticos", mas sim "O que se sabe sobre acorresponde ao que se sabe sobre" b" Estou curioso para saber se existem exemplos de código em que ter "Nan! = NaN" torne as coisas mais simples do que seriam?
Supercat

5
Teoricamente, se você tivesse NaN == NaN e não isNaN, ainda poderia testar NaN com !(x < 0 || x == 0 || x > 0), mas teria sido mais lento e desajeitado do que x != x.
User2357112 suporta Monica

50

NaN pode ser considerado como um estado / número indefinido. semelhante ao conceito de 0/0 sendo indefinido ou sqrt (-3) (no sistema de números reais em que o ponto flutuante mora).

NaN é usado como uma espécie de espaço reservado para esse estado indefinido. Matematicamente falando, indefinido não é igual a indefinido. Você também não pode dizer que um valor indefinido é maior ou menor que outro valor indefinido. Portanto, todas as comparações retornam falsas.

Esse comportamento também é vantajoso nos casos em que você compara sqrt (-3) com sqrt (-2). Ambos retornariam NaN, mas não são equivalentes, mesmo retornando o mesmo valor. Portanto, ter igualdade sempre retornando falso ao lidar com NaN é o comportamento desejado.


5
Qual deve ser o resultado de sqrt (1.00000000000000022) == sqrt (1.0)? Que tal (1E308 + 1E308-1E308-1E308-1E308) == (1E308 + 1E308)? Além disso, apenas cinco das seis comparações retornam falsas. O !=operador retorna verdadeiro. Ter NaN==NaNe NaN!=NaNambos retornar falso permitiriam que o código que compara x e y escolham o que deve acontecer quando os dois operandos forem NaN, escolhendo um ==ou outro !=.
Super10 #

38

Para lançar outra analogia. Se eu lhe der duas caixas e lhe disser que nenhuma delas contém uma maçã, você me diria que as caixas contêm a mesma coisa?

NaN não contém informações sobre o que é algo, apenas o que não é. Portanto, esses elementos nunca podem ser definitivamente considerados iguais.


6
Todos os conjuntos vazios são iguais, por definição.
MSalters

28
As caixas que você recebe NÃO são conhecidas por estar vazias.
22410 John Smith

7
Você poderia me dizer que as caixas não contêm a mesma coisa? Eu posso entender a lógica para (NaN==Nan)==false. O que eu não entendo é a razão de ser (Nan!=Nan)==true.
Supercat

3
Presumo que NaN! = NaN é verdadeiro porque x! = Y é definido como! (X == y). Concedido, não sei se a especificação IEEE define dessa maneira.
Kef Schecter 26/03

6
Mas nessa analogia, se você me desse uma caixa, dissesse que ela não continha maçãs e me perguntasse se era igual a si mesma, espera que eu diga não? Porque é isso que eu teria a dizer de acordo com o IEEE.
ponto

12

No artigo da wikipedia sobre NaN , as seguintes práticas podem causar NaNs:

  • Todas as operações matemáticas> com NaN como pelo menos um operando
  • As divisões 0/0, ∞ / ∞, ∞ / -∞, -∞ / ∞ e -∞ / -∞
  • As multiplicações 0 × ∞ e 0 × -∞
  • As adições ∞ + (-∞), (-∞) + ∞ e subtrações equivalentes.
  • Aplicação de uma função a argumentos fora de seu domínio, incluindo a raiz quadrada de um número negativo, o logaritmo de um número negativo, a tangente de um múltiplo ímpar de 90 graus (ou π / 2 radianos) ou o seno inverso ou cosseno de um número menor que -1 ou maior que +1.

Como não há como saber qual dessas operações criou o NaN, não há como compará-las que faça sentido.


3
Além disso, mesmo se você soubesse qual operação, não ajudaria. Posso construir qualquer número de fórmulas que vão para 0/0 em algum momento, que têm (se assumirmos continuidade) valores bem definidos e diferentes nesse ponto.
David Thornley

4

Não conheço a lógica do design, mas aqui está um trecho do padrão IEEE 754-1985:

"Deve ser possível comparar números de ponto flutuante em todos os formatos suportados, mesmo que os formatos dos operandos sejam diferentes. As comparações são exatas e nunca transbordam nem transbordam. Quatro relações mutuamente exclusivas são possíveis: menor que, igual, maior que e não ordenado O último caso surge quando pelo menos um operando é NaN. Todo NaN deve comparar sem ordem com tudo, inclusive ele próprio. "


2

Parece apenas peculiar porque a maioria dos ambientes de programação que permitem NaNs também não permite lógica com três valores. Se você jogar a lógica de 3 valores na mistura, ela se tornará consistente:

  • (2,7 == 2,7) = verdadeiro
  • (2,7 == 2,6) = falso
  • (2,7 == NaN) = desconhecido
  • (NaN == NaN) = desconhecido

Mesmo o .NET não fornece um bool? operator==(double v1, double v2)operador, portanto você ainda fica com o (NaN == NaN) = falseresultado bobo .


1

Eu estou supondo que NaN (Not A Number) significa exatamente isso: Este não é um número e, portanto, compará-lo não faz realmente sentido.

É um pouco como aritmética no SQL com nulloperandos: todos resultam em null.

As comparações para números de ponto flutuante comparam valores numéricos. Portanto, eles não podem ser usados ​​para valores não numéricos. Portanto, NaN não pode ser comparado em sentido numérico.


3
"Este não é um número e, portanto, compará-lo não faz muito sentido". Strings não são números, mas compará-los faz sentido.
jason

2
sim, comparar uma string com uma string faz sentido. Mas comparar uma sequência com, digamos, maçãs, não faz muito sentido. Como maçãs e peras não são números, faz sentido compará-las? Qual é maior?
Daren Thomas

@ DarenThomas: No SQL, nem "SE NULL = NULL THEN FOO;" nem "SE Nulo <> Nulo ENTÃO CHAMAR FOO;" [ou seja qual for a sintaxe] será executada FOO. Para NaN ser equivalente if (NaN != NaN) foo();, não deve ser executado foo, mas sim.
supercat

1

A resposta simplificada é que um NaN não tem valor numérico; portanto, não há nada nele para comparar com qualquer outra coisa.

Você pode testar e substituir seus NaNs por + INF se desejar que eles ajam como + INF.


0

Embora eu concorde que comparações de NaN com qualquer número real devam ser desordenadas, acho que há apenas uma razão para comparar NaN consigo mesmo. Como, por exemplo, descobrimos a diferença entre NaNs sinalizadores e NaNs silenciosos? Se pensarmos nos sinais como um conjunto de valores booleanos (isto é, um vetor de bits), pode-se perguntar se os vetores de bits são iguais ou diferentes e ordenar os conjuntos de acordo. Por exemplo, ao decodificar um expoente máximo tendencioso, se o significando fosse deixado alterado para alinhar o bit mais significativo do significando com o bit mais significativo do formato binário, um valor negativo seria um NaN silencioso e qualquer valor positivo seria ser um NaN sinalizador. É claro que zero é reservado para o infinito e a comparação seria desordenada. O alinhamento de MSB permitiria a comparação direta de sinais, mesmo de diferentes formatos binários. Dois NaNs com o mesmo conjunto de sinais seriam equivalentes e dariam sentido à igualdade.


-1

Para mim, a maneira mais fácil de explicar é:

Eu tenho algo e se não é uma maçã, é laranja?

Você não pode comparar o NaN com outra coisa (até ela mesma) porque ela não tem um valor. Também pode ser qualquer valor (exceto um número).

Eu tenho algo e se não é igual a um número, então é uma string?


O que você quer dizer com "pode ​​haver qualquer valor, exceto um número"?
PUSHKIN

-2

Porque a matemática é o campo onde os números "simplesmente existem". Na computação, você deve inicializar esses números e manter o estado deles de acordo com suas necessidades. Naquela época, a inicialização da memória funcionava da maneira que você nunca poderia confiar. Você nunca se permitiu pensar sobre isso "oh, isso seria inicializado com 0xCD o tempo todo, meu algo não está quebrado" .

Portanto, você precisa de um solvente não misturador adequado que seja pegajoso o suficiente para não deixar seu algoritmo ser sugado e quebrado. Bons algoritmos envolvendo números funcionam principalmente com relações, e aqueles se () relações serão omitidas.

Isso é apenas graxa que você pode colocar em uma nova variável na criação, em vez de programar o inferno aleatório a partir da memória do computador. E seu algoritmo, qualquer que seja, não será quebrado.

Em seguida, quando você descobrir subitamente que seu algoritmo está produzindo NaNs, é possível limpá-lo, analisando cada ramo, um de cada vez. Novamente, a regra "sempre falsa" está ajudando muito nisso.


-4

Resposta muito curta:

Porque o seguinte: nan / nan = 1 NÃO deve reter. De outra formainf/inf seria 1.

(Portanto, nannão pode ser igual a nan. Quanto a , >ou <, se nanrespeitaria qualquer relação de ordem em um conjunto que satisfaça a propriedade arquimediana, teríamos novamente nan / nan = 1no limite).


2
Não, isso não faz sentido. Temos inf = infe inf / inf = nan, portanto, também nan = nannão impedimos nan / nan = nan.
starblue

@starblue Você quer dizer nan / nan = 1? Enfim ... Seu raciocínio faz sentido se inf e nan forem exatamente como quaisquer outros números. Não é o caso. A razão pela qual inf/infdeve ser nan(ou forma indeterminada em matemática) e não 1é mais sutil que a simples manipulação algébrica (veja o teorema De L'Hospital).
SeF
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.