219 votos

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

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, ¿que opinas sobre esto?

331voto

SLaks Puntos 391154

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 son de 8 bytes de ancho, CanCompareBits erróneamente devuelve true, resultando en una comparación bit a bit de dos diferentes, pero semánticamente idénticas, valores.

Cuando al menos uno de los campos no es de 8 bytes de ancho, CanCompareBits devuelve false, y el código se procede a utilizar la reflexión para recorrer los campos y llame Equals para cada valor, el cual correctamente golosinas -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

56voto

Ben M Puntos 14458

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

El elemento central es la fuente de comentarios sobre CanCompareBits, que ValueType.Equals se utiliza para determinar si el uso de memcmp-estilo de la comparación:

El comentario de CanCompareBits dice "Return verdadero si el tipo de valor no contienen puntero y está estrechamente embalado". Y FastEqualsCheck uso "memcmp" para acelerar la comparación.

El autor pasa a estado exactamente el problema que se describe por las OP:

Imagina que tienes una estructura que sólo contiene un flotador. Lo que va a ocurrir si uno contiene +0.0, y el otro contiene-0.0? Ellos deben ser los mismo, pero la subyacente binario representación son diferentes. Si nido de otra estructura que anulan el método Equals, que la optimización también se producirá un error.

49voto

Eric Lippert Puntos 300275

Vilx la conjetura es correcta. Lo "CanCompareBits" no se comprueba si el tipo de valor en cuestión es "apretadas" en la memoria. Una apretada estructura se compararon mediante la simple comparación de los binarios de los bits que componen la estructura; un holgadamente estructura se compara llamando Igual en todos los miembros.

Esto explica SLaks observación de que repros con estructuras que son todas dobles; tales estructuras están siempre abarrotados.

Por desgracia, como hemos visto aquí, que introduce una diferencia semántica porque comparación bit a bit de dobles y es Igual a la comparación de dobles da resultados diferentes.

22voto

Vilx- Puntos 37939

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 ambos CanCompareBits() y FastEquals() (ambos métodos estáticos) son externo ( [MethodImpl(MethodImplOptions.InternalCall)] ) y no tienen ninguna fuente disponible.

Volver a conjeturar por qué un caso puede ser comparado por los pedacitos, y el otro no (alineación temas tal vez?)

17voto

Matthew Flaschen Puntos 131723

Eso le da cierto para mí, con gmcs de Mono 2.4.2.3.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X