¿Puede alguien 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, ¿qué piensas de esto?
11 answers
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 por error true, 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 false, y el código procede a usar reflexión para recorrer los campos y llamar a Equals
para cada valor, que trata correctamente -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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-05 16:15:13
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 sobre CanCompareBits
, que ValueType.Equals
utiliza para determinar si usar memcmp
-comparación de estilo:
El comentario de CanCompareBits dice "Devuelve true si el valuetype no contener puntero y es firmemente lleno". Y Uso rápido de Qualscheck "memcmp" para acelerar la comparación.
El autor continúa indique 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? Deberían ser los lo mismo, pero el binario subyacente la representación es diferente. Si anida otra estructura que anula el método Igual, esa optimización también fallará.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 15:56:44
La conjetura de Vilx es correcta. Lo que hace " CanCompareBits "es comprobar si el tipo de valor en cuestión está" apretado " en la memoria. Una estructura compacta se compara simplemente comparando los bits binarios que componen la estructura; una estructura poco compacta se compara llamando Iguales en todos los miembros.
Esto explica la observación de SLaks de que repro con estructuras que son todas dobles; tales estructuras siempre están bien empaquetadas.
Desafortunadamente, como hemos visto aquí, eso introduce una diferencia semántica porque la comparación bit a bit de dobles e Igual a la comparación de dobles da resultados diferentes.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 16:03:48
Media 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 fuente disponible.
Volver a adivinar por qué un caso puede ser comparado por bits, y el otro no (problemas de alineación tal vez?)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 15:53:01
hace dar verdadero para mí, con Mono gmcs 2.4.2.3.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 15:42:30
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 flotadores, pero solo ocurre si los campos en la estructura suman un múltiplo de 8 bytes.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 19:28:11
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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 15:52:59
{¿qué piensas de esto?
Siempre sobrescribe Equals y GetHashCode en los tipos de valor. Será rápido y correcto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-28 06:13:36
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 publicaría en. NET Core 2.1.0.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-24 07:17:57
Si haces D2 así
public struct D2
{
public double d;
public double f;
public string s;
}
Es verdad.
Si lo haces así
public struct D2
{
public double d;
public double f;
public double u;
}
Sigue siendo falso.
it parece que es falso si la estructura solo contiene dobles.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-22 10:34:50
Debe estar relacionado con cero, ya que cambiando la línea
D. d = -0.0
A:
D. d = 0,0
Resulta que la comparación es verdadera...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-24 15:50:34