Pergunta

Por que fazer comparações de valores NaN se comportam de forma diferente de todos os outros valores?Isto é, todas as comparações com os operadores ==, <=, >=, <, > onde um ou ambos os valores é o NaN retorna false, ao contrário do comportamento de todos os outros valores.

Eu suponho que isso simplifica cálculos numéricos, de alguma forma, mas eu não conseguia encontrar explícita razão, nem mesmo a Anotações de aula sobre o Status do IEEE 754 por Kahan que discute outras decisões de projeto em detalhes.

Este comportamento desviante está causando problemas ao fazer o simples processamento de dados.Por exemplo, quando a ordenação de uma lista de registos w.r.t.alguns valores reais de campo em um programa em C que eu preciso para escrever um código extra para lidar com NaN como o maior elemento, caso contrário, o algoritmo de classificação pode se tornar confuso.

Editar: As respostas até agora, todos argumentam que não faz sentido comparar os NaNs.

Eu concordo, mas isso não significa que a resposta correta é falso, em vez disso, ele seria um Não-um-Booleano (NaB), que, felizmente, não existe.

Por isso a escolha de retornar true ou false para comparações é, a meu ver, arbitrário, e para dados gerais de processamento seria vantajoso se ele obedecia a leis usuais (reflexividade de ==, tricotomia de <, ==, >), para que as estruturas de dados que dependem destas leis se tornam confusos.

Então, eu estou perguntando para alguns de concreto vantagem de quebrar essas leis, não apenas raciocínio filosófico.

Edit 2: Eu acho que eu entendo agora por que fazer NaN máxima seria uma má ideia, iria estragar o cálculo de limites superiores.

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

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

que, no entanto, devem ser melhor escrito comparando-se o valor absoluto da diferença com um pequeno limite.Então, IMHO este é um relativamente fraco argumento para quebrar a reflexividade em NaN.

Foi útil?

Solução

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

Primeiro, os 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 se mantém para carros alegóricos, nem mesmo a mais importante. Por exemplo:

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

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

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

Em relação ao seu comentário "isso não significa que a resposta correta seja falsa", isso está errado. O predicado (y < x) pergunta se y é menos do 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 mantém pelos valores de ponto flutuante. No entanto, existe uma propriedade semelhante que se mantém. Cláusula 5.11, parágrafo 2 do padrão 754-2008:

Quatro relações mutuamente exclusivas são possíveis: menos que, igual, maiores que e não ordenadas. O último caso surge quando pelo menos um operando é NAN. Cada NAN deve comparar não ordenados com tudo, inclusive a si mesmo.

No que diz respeito a escrever código extra para lidar com os Nans, geralmente é possível (embora nem sempre seja fácil) estruturar seu código de tal maneira que os Nans caem corretamente, mas esse nem sempre é o caso. Quando não é, pode ser necessário algum código extra, 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 com o argumento de que a adoção de Nan! = Nan não parece preservar nenhum axioma familiar. Confesso ter alguma simpatia por esse ponto de vista, então pensei em revisitar essa resposta e fornecer um pouco mais de contexto.

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

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

  • Mais importante ainda, não havia isnan( ) predicado na época em que a NAN foi formalizada na aritmética de 8087; Era necessário fornecer aos programadores um meio conveniente e eficiente de detectar valores de NAN que não dependiam de linguagens de programação, fornecendo algo como isnan( ) o que pode levar muitos anos. Vou citar a própria escrita de Kahan sobre o assunto:

Não havia como se livrar dos Nans, eles seriam tão inúteis quanto os indefinidos em Crays; Assim que um foi encontrado, a computação seria melhor interrompida do que continuar por um tempo indefinido para uma conclusão indefinida. É por isso que algumas operações sobre os NANs devem fornecer resultados não-NAN. Quais operações? … As exceções são c predicadas "x == x" e "x! = X", que são respectivamente 1 e 0 para cada número infinito ou finito x, mas reverso se x não for um número (NAN); Eles fornecem a única distinção simples e não 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-a-boolean". Talvez esse pragmatismo tenha sido extraviado, e o padrão deveria ter exigido isnan( ), mas isso tornaria a NAN quase impossível de usar de maneira eficiente e conveniente por vários anos, enquanto o mundo esperava 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 viver com ele do que reclamar na Internet. Se você quiser argumentar que uma relação de pedido adequada para contêineres deve também Existir, eu recomendaria defender que sua linguagem de programação favorita implemente o totalOrder predicado padronizado no IEEE-754 (2008). O fato de ainda não falar com a validade da preocupação de Kahan que motivou o estado atual das coisas.

Outras dicas

A NAN pode ser considerada como um estado/número indefinido. Semelhante ao conceito de 0/0 sendo indefinido ou SQRT (-3) (no sistema de números real onde o ponto flutuante vive).

A NAN é usada como uma espécie de espaço reservado para este 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 que retornem o mesmo valor. Portanto, ter igualdade sempre retornando falsa ao lidar com a NAN é o comportamento desejado.

Para jogar mais uma analogia. Se eu entregar duas caixas e lhe dizer que nenhum deles contém uma maçã, você me diria que as caixas contêm a mesma coisa?

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

Do artigo da Wikipedia sobre Nan, as seguintes práticas podem causar Nans:

  • Todas as operações matemáticas> com uma 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.
  • Aplicando uma função a argumentos fora de seu domínio, incluindo a raiz quadrada de um número negativo, tomando o logaritmo de um número negativo, tomando a tangente de um múltiplo ímpar de 90 graus (ou π/2 radianos) ou tomando o seno inverso seno ou cosseno de um número inferior a -1 ou maior que +1.

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

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 subirem. Quatro relações mutuamente exclusivas são possíveis: menos que, igual, maior que e não ordenadas . O último caso surge quando pelo menos um operando é Nan. Cada NAN deve comparar não ordenados com tudo, inclusive em si. "

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

  • (2.7 == 2.7) = true
  • (2.7 == 2.6) = false
  • (2.7 == nan) = desconhecido
  • (Nan == nan) = desconhecido

Mesmo .NET não fornece um bool? operator==(double v1, double v2) operador, então você ainda está preso com o bobo (NaN == NaN) = false resultado.

Eu estou supondo que o NaN (Não é Um Número) significa exatamente isso:Este não é um número e, portanto, a comparação não faz sentido.

É um pouco como a aritmética no SQL com null operandos:Todos eles resultam em null.

As comparações para números de ponto flutuante comparar valores numéricos.Assim, eles não podem ser utilizados para não valores numéricos.NaN, portanto, não podem ser comparados em um sentido numérico.

A resposta super simplificada é que uma NAN não tem valor numérico, portanto, não há nada para comparar com qualquer outra coisa.

Você pode considerar testar e substituir seus Nans por +Inf, se quiser que eles agem como +inf.

A NAN é uma nova instância implícita (de um tipo especial de erro de tempo de execução). Que significa NaN !== NaN pela mesma razão que new Error !== new Error;

E lembre /a/ !== /a/ que é apenas uma sintaxe açúcar para new RegExp('a') !== new RegExp('a')

Embora eu concorde que as comparações de NAN com qualquer número real devem não ser ordenadas, acho que há apenas motivo para comparar a NAN com si mesma. Como, por exemplo, descobre a diferença entre nans sinalizadores e nans nans silenciosos? Se pensarmos nos sinais como um conjunto de valores booleanos (ou seja, 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 tendencioso máximo, se o significado foi deixado mudado de modo a alinhar a parte mais significativa do significado da parte mais significativa do formato binário, um valor negativo seria uma nan silenciosa e qualquer valor positivo seria ser uma nan sinalizadora. O zero, é claro, é reservado para o infinito e a comparação não seria ordenada. O alinhamento do MSB permitiria a comparação direta de sinais, mesmo de diferentes formatos binários. Dois Nans com o mesmo conjunto de sinais seriam, portanto, equivalentes e dariam sentido à igualdade.

Porque a matemática é o campo em que os números "simplesmente existem". Na computação você deve inicializar Esses números e guarda o estado deles de acordo com suas necessidades. Naqueles velhos tempos, a inicialização da memória funcionava da maneira que você nunca poderia confiar. Você nunca poderia se permitir pensar sobre isso "Oh, isso seria inicializado com 0xcd o tempo todo, meu algo não vai quebrar".

Então você precisa não misturando solvente que é pegajoso o suficiente Não não deixar seu algoritmo ser sugado e quebrado. Bons algoritmos envolvendo números vão funcionar com as relações, e aqueles E se() as relações serão omitidas.

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

Em seguida, quando você ainda de repente descobriu que seu algoritmo está produzindo Nans, é possível limpá -lo, olhando para cada ramo um de cada vez. Novamente, a regra "sempre falsa" está ajudando muito nisso.

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

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

Você não pode comparar a NAN com outra coisa (mesmo) porque 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, é uma string?

Resposta muito curta:

Porque o seguinte: nan / nan = 1 não deve segurar. Por outro lado inf/inf seria 1.

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top