Aqui está o exemplo com comentários:
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
}
}
Então, o que você pensa sobre isso?
c#
.net
floating-point
Alexander Efimov
fonte
fonte
c.d.Equals(d.d)
avalia atrue
como o fazc.f.Equals(d.f)
Respostas:
O erro está nas duas linhas a seguir
System.ValueType
: (Entrei na fonte de referência)(Ambos os métodos são
[MethodImpl(MethodImplOptions.InternalCall)]
)Quando todos os campos têm 8 bytes de largura,
CanCompareBits
retorna true por engano, resultando em uma comparação bit a bit de dois valores diferentes, mas semanticamente idênticos.Quando pelo menos um campo não tem 8 bytes de largura,
CanCompareBits
retorna false e o código continua a usar a reflexão para fazer um loop sobre os campos e chamarEquals
cada valor, que é corretamente tratado-0.0
como igual a0.0
.Aqui está a fonte
CanCompareBits
do SSCLI:fonte
IsNotTightlyPacked
.The bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
Encontrei a resposta em http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .
A peça principal é o comentário da fonte
CanCompareBits
, que éValueType.Equals
usado para determinar se é usada amemcmp
comparação de estilo:O autor continua declarando exatamente o problema descrito pelo OP:
fonte
Equals(Object)
paradouble
,float
eDecimal
mudou durante os primeiros rascunhos de .net; Eu acho que é mais importante que o virtualX.Equals((Object)Y)
retorne apenastrue
quandoX
eY
é indistinguível, do que fazer com que o método corresponda ao comportamento de outras sobrecargas (especialmente porque, devido à coerção implícita do tipo, osEquals
métodos sobrecarregados nem definem uma relação de equivalência !, por exemplo,1.0f.Equals(1.0)
gera false, mas1.0.Equals(1.0f)
gera true!) O verdadeiro problema do IMHO não é com a maneira como as estruturas são comparadas ... #Equals
significa algo diferente de equivalência. Suponha, por exemplo, que se queira escrever um método que pegue um objeto imutável e, se ainda não foi armazenado em cache, o executaToString
e armazena em cache o resultado; se tiver sido armazenado em cache, simplesmente retorne a sequência em cache. Não é uma coisa irracional, mas falharia malDecimal
porque dois valores podem comparar iguais, mas produzir seqüências diferentes.A conjectura de Vilx está correta. O que "CanCompareBits" faz é verificar se o tipo de valor em questão está "compactado" na memória. Uma estrutura compactada é comparada simplesmente comparando os bits binários que compõem a estrutura; uma estrutura fracamente compacta é comparada chamando Igual a todos os membros.
Isso explica a observação de SLaks de que ele é reprogramado com estruturas que são todas duplas; tais estruturas são sempre bem compactadas.
Infelizmente, como vimos aqui, isso introduz uma diferença semântica porque a comparação bit a bit de duplas e a comparação igual de duplas fornece resultados diferentes.
fonte
Meia resposta:
Refletor nos diz que
ValueType.Equals()
faz algo parecido com isto:Infelizmente, ambos
CanCompareBits()
eFastEquals()
(ambos os métodos estáticos) são externos ([MethodImpl(MethodImplOptions.InternalCall)]
) e não têm fonte disponível.Voltando a adivinhar por que um caso pode ser comparado por bits e o outro não (problemas de alinhamento, talvez?)
fonte
Isso é verdadeiro para mim, com os gmcs do Mono 2.4.2.3.
fonte
Caso de teste mais simples:
EDIT : O bug também acontece com carros alegóricos, mas só acontece se os campos na estrutura somam um múltiplo de 8 bytes.
fonte
double
é0
. Você está errado.Ele deve estar relacionado à comparação pouco a pouco, pois
0.0
deve diferir-0.0
apenas do bit de sinal.fonte
Sempre substitua Equals e GetHashCode nos tipos de valor. Será rápido e correto.
fonte
Apenas uma atualização para esse bug de 10 anos: foi corrigida ( Isenção de responsabilidade : sou o autor deste PR) no .NET Core, que provavelmente seria lançado no .NET Core 2.1.0.
A postagem do blog explicou o erro e como eu o corrigi.
fonte
Se você faz D2 assim
é verdade.
se você faz assim
Ainda é falso.
i t parece que é falso se a estrutura só tem duplos.
fonte
Deve ser zero, pois alterar a linha
para:
resulta na comparação verdadeira ...
fonte