Aquí está el ejemplo con comentarios:
class Program
{
// first version of structure
public struct D1
{
public double d;
public int f;
}
// during some changes in code then we got D2 from D1
// Field f type became double while it was int before
public struct D2
{
public double d;
public double f;
}
static void Main(string[] args)
{
// Scenario with the first version
D1 a = new D1();
D1 b = new D1();
a.f = b.f = 1;
a.d = 0.0;
b.d = -0.0;
bool r1 = a.Equals(b); // gives true, all is ok
// The same scenario with the new one
D2 c = new D2();
D2 d = new D2();
c.f = d.f = 1;
c.d = 0.0;
d.d = -0.0;
bool r2 = c.Equals(d); // false! this is not the expected result
}
}
Entonces, ¿qué te parece esto?
c#
.net
floating-point
Alexander Efimov
fuente
fuente
c.d.Equals(d.d)
evalúatrue
como lo hacec.f.Equals(d.f)
Respuestas:
El error está en las siguientes dos líneas de
System.ValueType
: (Entré en la fuente de referencia)(Ambos métodos son
[MethodImpl(MethodImplOptions.InternalCall)]
)Cuando todos los campos tienen 8 bytes de ancho, por
CanCompareBits
error devuelve verdadero, lo que resulta en una comparación bit a bit de dos valores diferentes, pero semánticamente idénticos.Cuando al menos un campo no tiene 8 bytes de ancho,
CanCompareBits
devuelve falso, y el código continúa usando la reflexión para recorrer los campos y llamarEquals
a cada valor, que se trata correctamente-0.0
como igual a0.0
.Aquí está la fuente
CanCompareBits
de SSCLI:fuente
IsNotTightlyPacked
.The bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
Encontré la respuesta en http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .
La pieza central es el comentario fuente
CanCompareBits
, que seValueType.Equals
usa para determinar si se debe usar lamemcmp
comparación de estilos:El autor continúa exponiendo exactamente el problema descrito por el OP:
fuente
Equals(Object)
paradouble
,float
yDecimal
cambiado durante los primeros borradores de .NET; Me gustaría pensar que es más importante contar con lo virtualX.Equals((Object)Y)
única recompensatrue
cuandoX
yY
son indistinguibles, que tener ese método coincide con el comportamiento de otros sobrecargas (sobre todo teniendo en cuenta que, debido a la conversión de tipos implícita, sobrecargadosEquals
métodos ni siquiera definen una relación de equivalencia !, por ejemplo,1.0f.Equals(1.0)
da falso, pero1.0.Equals(1.0f)
da verdadero!) El verdadero problema en mi humilde opinión no es con la forma en que se comparan las estructuras ...Equals
para significar algo más que equivalencia. Supongamos, por ejemplo, que uno quiere escribir un método que toma un objeto inmutable y, si aún no se ha almacenado en caché, se ejecutaToString
en él y almacena en caché el resultado; Si se ha almacenado en caché, simplemente devuelva la cadena en caché. No es una cosa irrazonable, pero fracasaría muchoDecimal
ya que dos valores pueden ser iguales pero producir cadenas diferentes.La conjetura de Vilx es correcta. Lo que hace "CanCompareBits" es verificar si el tipo de valor en cuestión está "apretado" en la memoria. Una estructura apretada se compara simplemente comparando los bits binarios que componen la estructura; una estructura compacta se compara llamando a Equals en todos los miembros.
Esto explica la observación de SLaks de que repros con estructuras que son todas dobles; tales estructuras siempre están apretadas.
Desafortunadamente, como hemos visto aquí, eso introduce una diferencia semántica porque la comparación bit a bit de dobles y la comparación igual a dobles da resultados diferentes.
fuente
Media respuesta:
Reflector nos dice que
ValueType.Equals()
hace algo como esto:Desafortunadamente, ambos
CanCompareBits()
yFastEquals()
(ambos métodos estáticos) son externos ([MethodImpl(MethodImplOptions.InternalCall)]
) y no tienen una fuente disponible.Volviendo a adivinar por qué un caso puede ser comparado por bits, y el otro no (¿problemas de alineación tal vez?)
fuente
Que no dan cierto para mí, la GMC de Mono 2.4.2.3.
fuente
Caso de prueba más simple:
EDITAR : El error también ocurre con flotantes, pero solo ocurre si los campos en la estructura suman un múltiplo de 8 bytes.
fuente
double
es0
. Te equivocas.Debe estar relacionado con una comparación bit a bit, ya que
0.0
debe diferir de-0.0
solo el bit de señal.fuente
Siempre anule Equals y GetHashCode en los tipos de valor. Será rápido y correcto.
fuente
Solo una actualización para este error de 10 años: se ha solucionado ( Descargo de responsabilidad : soy el autor de este PR) en .NET Core que probablemente se lanzaría en .NET Core 2.1.0.
La publicación del blog explicaba el error y cómo lo solucioné.
fuente
Si haces D2 así
es verdad.
si lo haces así
Sigue siendo falso.
i t parece que es falso si la estructura sólo se mantiene dobles.
fuente
Debe estar relacionado con cero, ya que cambiar la línea
a:
da como resultado que la comparación sea verdadera ...
fuente