Al comparar valores de coma flotante para igualdad, hay dos enfoques diferentes:
NaN
no es igual a sí mismo, lo que coincide con la especificación IEEE 754 .NaN
ser igual a sí mismo, lo que proporciona la propiedad matemática de la reflexividad que es esencial para la definición de una relación de equivalencia
Los tipos de punto flotante IEEE integrados en C # ( float
y double
) siguen la semántica IEEE para ==
y !=
(y los operadores relacionales como <
) pero aseguran la reflexividad para object.Equals
, IEquatable<T>.Equals
(y CompareTo
).
Ahora considere una biblioteca que proporciona estructuras vectoriales sobre float
/ double
. Tal tipo de vector sobrecargaría ==
/ !=
y anularía object.Equals
/ IEquatable<T>.Equals
.
En lo que todos están de acuerdo es que ==
/ !=
debería seguir la semántica de IEEE. La pregunta es, si dicha biblioteca implementa el Equals
método (que está separado de los operadores de igualdad) de una manera reflexiva o que coincida con la semántica IEEE.
Argumentos para usar la semántica IEEE para Equals
:
- Sigue a IEEE 754
Es (posiblemente mucho) más rápido porque puede aprovechar las instrucciones SIMD
He hecho una pregunta por separado sobre stackoverflow sobre cómo expresaría la igualdad reflexiva utilizando las instrucciones SIMD y su impacto en el rendimiento: instrucciones SIMD para la comparación de igualdad de punto flotante
Actualización: Parece que es posible implementar la igualdad reflexiva de manera eficiente utilizando tres instrucciones SIMD.
La documentación para
Equals
no requiere reflexividad cuando involucra punto flotante:Las siguientes declaraciones deben ser ciertas para todas las implementaciones del método Equals (Object). En la lista,
x
,y
, yz
representan referencias a objetos que no son nulos.x.Equals(x)
retornostrue
, excepto en casos que involucran tipos de punto flotante. Ver ISO / IEC / IEEE 60559: 2011, Tecnología de la información - Sistemas de microprocesador - Aritmética de punto flotante.Si usa flotadores como teclas de diccionario, está viviendo en un estado de pecado y no debe esperar un comportamiento sensato.
Argumentos para ser reflexivo:
Es compatible con los tipos existentes, incluyendo
Single
,Double
,Tuple
ySystem.Numerics.Complex
.No conozco ningún precedente en el BCL donde
Equals
siga IEEE en lugar de ser reflexivo. Contraejemplos incluyenSingle
,Double
,Tuple
ySystem.Numerics.Complex
.Equals
es utilizado principalmente por contenedores y algoritmos de búsqueda que dependen de la reflexividad. Para estos algoritmos, una ganancia de rendimiento es irrelevante si les impide funcionar. No sacrifique la corrección por el rendimiento.- Rompe todos los conjuntos y diccionarios en base de hash,
Contains
,Find
,IndexOf
en varias colecciones / LINQ, LINQ operaciones de conjunto basada (Union
,Except
, etc.) si los datos contienenNaN
valores. El código que realiza cálculos reales donde IEEE semántico es aceptable generalmente funciona en tipos y usos concretos
==
/!=
(o más probablemente en comparaciones épsilon).Actualmente no puede escribir cálculos de alto rendimiento utilizando genéricos, ya que necesita operaciones aritméticas para eso, pero estos no están disponibles a través de interfaces / métodos virtuales.
Por lo tanto, un
Equals
método más lento no afectaría a la mayoría de los códigos de alto rendimiento.Es posible proporcionar un
IeeeEquals
método oIeeeEqualityComparer<T>
para los casos en los que necesita la semántica IEEE o necesita una ventaja de rendimiento.
En mi opinión, estos argumentos favorecen fuertemente una implementación reflexiva.
El equipo CoreFX de Microsoft planea introducir dicho tipo de vector en .NET. A diferencia de mí , prefieren la solución IEEE , principalmente debido a las ventajas de rendimiento. Dado que tal decisión ciertamente no cambiará después de un lanzamiento final, quiero recibir comentarios de la comunidad, sobre lo que creo que es un gran error.
fuente
==
yEquals
devolvería resultados diferentes. Muchos programadores asumen que lo son y hacen lo mismo . Además, en general, las implementaciones de los operadores de igualdad invocan elEquals
método. Usted ha argumentado que uno podría incluir aIeeeEquals
, pero también podría hacerlo al revés e incluir unReflexiveEquals
método-. ElVector<float>
tipo puede usarse en muchas aplicaciones críticas de rendimiento y debe optimizarse en consecuencia.float
/double
y varios otros tipos,==
yEquals
ya son diferentes. Creo que una inconsistencia con los tipos existentes sería aún más confusa que la inconsistencia entre==
yEquals
aún tendrá que lidiar con otros tipos. 2) Casi todos los algoritmos / colecciones genéricos usanEquals
y dependen de su reflexividad para funcionar (LINQ y diccionarios), mientras que los algoritmos concretos de punto flotante generalmente usan de==
dónde obtienen su semántica IEEE.Vector<float>
una "bestia" diferente a una simplefloat
odouble
. Según esa medida, no puedo ver la razónEquals
o el==
operador para cumplir con los estándares de ellos. Usted mismo dijo: "Si está usando flotadores como teclas del diccionario, está viviendo en un estado de pecado y no debe esperar un comportamiento sensato". Si uno fuera a almacenarNaN
en un diccionario, entonces es su propia condenada culpa por usar una práctica terrible. No creo que el equipo CoreFX no haya pensado en esto. Yo iría con unoReflexiveEquals
o similar, solo por el bien del rendimiento.Respuestas:
Yo diría que el comportamiento de IEEE es correcto.
NaN
s no son equivalentes entre sí de ninguna manera; corresponden a condiciones mal definidas donde una respuesta numérica no es apropiada.Más allá de los beneficios de rendimiento que provienen del uso de la aritmética IEEE que la mayoría de los procesadores admiten de forma nativa, creo que hay un problema semántico al decir que si
isnan(x) && isnan(y)
, entoncesx == y
. Por ejemplo:Yo diría que no hay una razón significativa por la que uno consideraría
x
igual ay
. Difícilmente podría concluir que son números equivalentes; no son números en absoluto, por lo que parece un concepto completamente inválido.Además, desde una perspectiva de diseño de API, si está trabajando en una biblioteca de propósito general que está destinada a ser utilizada por muchos programadores, tiene sentido usar la semántica de punto flotante más típica de la industria. El objetivo de una buena biblioteca es ahorrar tiempo para quienes la usan, por lo que construir un comportamiento no estándar está listo para la confusión.
fuente
NaN == NaN
debería devolver falso es indiscutible. La pregunta es qué.Equals
debe hacer el método. Por ejemplo, si lo usoNaN
como clave de diccionario, el valor asociado se vuelve irrecuperable siNaN.Equals(NaN)
devuelve falso.Single
,Double
clases, etc. ya tienen el comportamiento reflexivo. En mi humilde opinión, esa fue la decisión equivocada para empezar. Pero no dejaría que la elegancia se interpusiera en la utilidad / velocidad.==
que siempre ha seguido a IEEE, por lo que obtendrían el código rápido sin importar cómoEquals
se implemente. En mi opinión, todo el punto de tener unEquals
método separado es usar algoritmos que no se preocupan por el tipo concreto, como laDistinct()
función de LINQ .==
operador y unaEquals()
función que tienen una semántica diferente. Creo que está pagando un costo de confusión potencial desde la perspectiva del desarrollador, sin ningún beneficio real (no asigno ningún valor para poder usar un vector de números como clave de diccionario). Es solo mi opinión; No creo que haya una respuesta objetiva a la pregunta en cuestión.Hay un problema: IEEE754 define las operaciones relacionales y la igualdad de una manera adecuada para aplicaciones numéricas. No es adecuado para la clasificación y el hash. Entonces, si desea ordenar una matriz basada en valores numéricos, o si desea agregar valores numéricos a un conjunto o usarlos como claves en un diccionario, declara que los valores NaN no están permitidos o no usa IEEE754 operaciones integradas. Su tabla hash tendría que asegurarse de que todos los NaN coincidan con el mismo valor y compararlos entre sí.
Si define Vector, debe tomar la decisión de diseño si desea usarlo solo con fines numéricos o si debe ser compatible con la clasificación y el hash. Personalmente, creo que el propósito numérico debería ser mucho más importante. Si se necesita la clasificación / hashing, puede escribir una clase con Vector como miembro y definir el hashing y la igualdad en esa clase de la manera que desee.
fuente
==
y!=
para ellos. En mi experiencia, elEquals
método es utilizado solo por algoritmos no numéricos.