Ao comparar valores de ponto flutuante para igualdade, existem duas abordagens diferentes:
NaN
não sendo igual a si próprio, o que corresponde à especificação IEEE 754 .NaN
sendo igual a si mesmo, o que fornece a propriedade matemática da Reflexividade, essencial para a definição de uma relação de Equivalência
Os construídas em IEEE flutuante tipos de ponto em C # ( float
e double
) acompanhar semântica para IEEE ==
e !=
(e os operadores relacionais como <
) mas assegurar reflexividade para object.Equals
, IEquatable<T>.Equals
(e CompareTo
).
Agora considere uma biblioteca que fornece estruturas vetoriais sobre float
/ double
. Esse tipo de vetor sobrecarregaria ==
/ !=
e substituía object.Equals
/ IEquatable<T>.Equals
.
O que todo mundo concorda é que ==
/ !=
deve seguir a semântica do IEEE. A questão é: essa biblioteca deve implementar o Equals
método (que é separado dos operadores de igualdade) de maneira reflexiva ou de acordo com a semântica do IEEE.
Argumentos para usar a semântica IEEE para Equals
:
- Segue IEEE 754
É (possivelmente muito) mais rápido, porque pode tirar proveito das instruções do SIMD
Fiz uma pergunta separada no stackoverflow sobre como você expressaria igualdade reflexiva usando instruções SIMD e seu impacto no desempenho: instruções SIMD para comparação de igualdade de ponto flutuante
Atualização: parece possível implementar a igualdade reflexiva com eficiência usando três instruções SIMD.
A documentação para
Equals
não requer reflexividade ao envolver ponto flutuante:As instruções a seguir devem ser verdadeiras para todas as implementações do método Equals (Object). Na lista,
x
,y
, ez
representam referências a objetos que não são nulos.x.Equals(x)
retornatrue
, exceto nos casos que envolvem tipos de ponto flutuante. Consulte ISO / IEC / IEEE 60559: 2011, Tecnologia da informação - Sistemas de microprocessadores - Aritmética de ponto flutuante.Se você estiver usando carros alegóricos como chaves de dicionário, estará vivendo um estado de pecado e não deve esperar um comportamento sensato.
Argumentos para ser reflexivo:
É consistente com os tipos existentes, incluindo
Single
,Double
,Tuple
eSystem.Numerics.Complex
.Eu não conheço nenhum precedente na BCL onde
Equals
segue o IEEE em vez de ser reflexivo. Contra-exemplos incluemSingle
,Double
,Tuple
eSystem.Numerics.Complex
.Equals
é usado principalmente por contêineres e algoritmos de busca que dependem da reflexividade. Para esses algoritmos, um ganho de desempenho é irrelevante se os impedir de funcionar. Não sacrifique a correção pelo desempenho.- Rompe todos os conjuntos de hash base e dicionários,
Contains
,Find
,IndexOf
em várias colecções / LINQ, operações conjunto baseado LINQ (Union
,Except
, etc.), se os dados contémNaN
valores. O código que faz cálculos reais em que a semântica do IEEE é aceitável geralmente funciona em tipos concretos e usa
==
/!=
(ou comparações epsilon mais prováveis).Atualmente, não é possível gravar cálculos de alto desempenho usando genéricos, pois você precisa de operações aritméticas para isso, mas elas não estão disponíveis por meio de interfaces / métodos virtuais.
Portanto, um
Equals
método mais lento não afetaria a maioria dos códigos de alto desempenho.É possível fornecer um
IeeeEquals
método ou umIeeeEqualityComparer<T>
para os casos em que você precisa da semântica IEEE ou da vantagem de desempenho.
Na minha opinião, esses argumentos favorecem fortemente uma implementação reflexiva.
A equipe CoreFX da Microsoft planeja introduzir esse tipo de vetor no .NET. Ao contrário de mim, eles preferem a solução IEEE , principalmente devido às vantagens de desempenho. Como essa decisão certamente não será alterada após o lançamento final, quero receber feedback da comunidade sobre o que acredito ser um grande erro.
float
/ double
e vários outros tipos, ==
e Equals
já são diferentes. Eu acho que uma inconsistência com os tipos existentes seria ainda mais confusa do que a inconsistência entre ==
e Equals
você ainda terá que lidar com outros tipos. 2) Praticamente todos os algoritmos / coleções genéricos usam Equals
e dependem de sua reflexividade para funcionar (LINQ e dicionários), enquanto algoritmos concretos de ponto flutuante geralmente usam ==
onde obtêm sua semântica IEEE.
Vector<float>
um "animal" diferente de um simples float
ou double
. Por essa medida, não vejo o motivo Equals
ou o ==
operador de cumprir os padrões deles. Você mesmo disse: "Se você estiver usando carros alegóricos como chaves de dicionário, estará vivendo um estado de pecado e não deve esperar um comportamento sensato". Se alguém armazena NaN
em um dicionário, a culpa é deles por usar práticas terríveis. Eu dificilmente acho que a equipe CoreFX não tenha pensado nisso. Eu iria com um ReflexiveEquals
ou similar, apenas por uma questão de desempenho.
==
eEquals
retornaria resultados diferentes. Muitos programadores assumem que são e fazem a mesma coisa . Além disso - em geral, implementações dos operadores de igualdade invocam oEquals
método. Você argumentou que se pode incluir umIeeeEquals
, mas também se pode fazer o contrário e incluir umReflexiveEquals
método. OVector<float>
tipo-pode ser usado em muitos aplicativos críticos para o desempenho e deve ser otimizado de acordo.