typeparam example cref c# .net floating-point

example - ¿Alguien puede explicar este extraño comportamiento con flotadores firmados en C#?



summary example c# (11)

…¿qué piensas sobre esto?

Siempre anule Equals y GetHashCode en los tipos de valor. Será rápido y correcto.

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é piensas de esto?


Caso de prueba más simple:

Console.WriteLine("Good: " + new Good().Equals(new Good { d = -.0 })); Console.WriteLine("Bad: " + new Bad().Equals(new Bad { d = -.0 })); public struct Good { public double d; public int f; } public struct Bad { public double d; }

EDITAR : El error también ocurre con flotantes, pero solo ocurre si los campos en la estructura se suman a un múltiplo de 8 bytes.


Da verdad para mí, con gmcs de Mono 2.4.2.3.


Debe estar relacionado con cero, ya que cambiando la línea

dd = -0.0

a:

dd = 0.0

resultados en la comparación siendo verdad ...


Debe estar relacionado con una comparación bit a bit, ya que 0.0 debe diferir de -0.0 solo por el bit de señal.


El error está en las siguientes dos líneas de System.ValueType : (Entré en la fuente de referencia)

if (CanCompareBits(this)) return FastEqualsCheck(thisObj, obj);

(Ambos métodos son [MethodImpl(MethodImplOptions.InternalCall)] )

Cuando todos los campos tienen 8 bytes de ancho, CanCompareBits devuelve erróneamente verdadero, lo que resulta en una comparación a nivel de bits 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 procede a usar la reflexión para recorrer los campos y llamar a Equals para cada valor, lo que trata a -0.0 como igual a 0.0 .

Aquí está la fuente para CanCompareBits de SSCLI:

FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj) { WRAPPER_CONTRACT; STATIC_CONTRACT_SO_TOLERANT; _ASSERTE(obj != NULL); MethodTable* mt = obj->GetMethodTable(); FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked()); } FCIMPLEND


Encontré la respuesta en http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .

La pieza principal es el comentario fuente sobre CanCompareBits , que ValueType.Equals usa para determinar si se debe usar la comparación de estilo memcmp :

El comentario de CanCompareBits dice "Devuelva verdadero si el tipo de valor no contiene puntero y está empaquetado". Y FastEqualsCheck usa "memcmp" para acelerar la comparación.

El autor continúa explicando exactamente el problema descrito por el OP:

Imagina que tienes una estructura que solo contiene un flotador. ¿Qué ocurrirá si uno contiene +0.0, y el otro contiene -0.0? Deben ser los mismos, pero la representación binaria subyacente es diferente. Si anida otra estructura que anule el método Equals, la optimización también fallará.


La conjetura de Vilx es correcta. Lo que "CanCompareBits" hace es verificar si el tipo de valor en cuestión está "bien empaquetado" en la memoria. Una estructura bien empaquetada se compara simplemente comparando los bits binarios que forman la estructura; se compara una estructura poco estructurada llamando a Equals en todos los miembros.

Esto explica la observación de SLaks de que se reprosca 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 a nivel de bits de los dobles y la comparación de iguales de los dobles da resultados diferentes.


La mitad de una respuesta:

Reflector nos dice que ValueType.Equals() hace algo como esto:

if (CanCompareBits(this)) return FastEqualsCheck(this, obj); else // Use reflection to step through each member and call .Equals() on each one.

Desafortunadamente, tanto CanCompareBits() como FastEquals() (ambos métodos estáticos) son externos ( [MethodImpl(MethodImplOptions.InternalCall)] ) y no tienen ninguna fuente disponible.

Volver a adivinar por qué un caso puede compararse por bits y el otro no (¿problemas de alineación, tal vez?)


Si haces D2 así

public struct D2 { public double d; public double f; public string s; }

es verdad.

si lo haces asi

public struct D2 { public double d; public double f; public double u; }

Sigue siendo falso

Parece que es falso si la estructura solo se duplica.


Solo una actualización para este error de 10 años: se ha corregido ( 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 explicó el error y cómo lo solucioné.