Voici l'exemple avec des commentaires:
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
}
}
Alors, qu'est-ce que tu en penses?
c#
.net
floating-point
Alexander Efimov
la source
la source
c.d.Equals(d.d)
vatrue
de mêmec.f.Equals(d.f)
Réponses:
Le bug est dans les deux lignes suivantes de
System.ValueType
: (je suis entré dans la source de référence)(Les deux méthodes sont
[MethodImpl(MethodImplOptions.InternalCall)]
)Lorsque tous les champs ont une largeur de 8 octets,
CanCompareBits
renvoie par erreur true, ce qui entraîne une comparaison au niveau du bit de deux valeurs différentes, mais sémantiquement identiques.Lorsqu'au moins un champ ne fait pas 8 octets de large,
CanCompareBits
renvoie false et le code continue à utiliser la réflexion pour boucler sur les champs et appelerEquals
chaque valeur, qui est correctement traitée-0.0
comme égale à0.0
.Voici la source
CanCompareBits
de SSCLI:la source
IsNotTightlyPacked
.The bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
J'ai trouvé la réponse sur http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .
L'élément central est le commentaire source sur
CanCompareBits
, qui permetValueType.Equals
de déterminer s'il faut utiliser lamemcmp
comparaison -style:L'auteur poursuit en énonçant exactement le problème décrit par le PO:
la source
Equals(Object)
pourdouble
,float
etDecimal
changé au cours des premières ébauches de .net; Je pense qu'il est plus important d'avoir leX.Equals((Object)Y)
retour virtuel uniquementtrue
lorsqueX
et neY
peuvent être distingués, que de faire correspondre cette méthode au comportement d'autres surcharges (d'autant plus que, en raison de la contrainte de type implicite, lesEquals
méthodes surchargées ne définissent même pas une relation d'équivalence !, par exemple1.0f.Equals(1.0)
donne faux, mais1.0.Equals(1.0f)
donne vrai!) Le vrai problème àEquals
pour signifier autre chose que l'équivalence. Supposons, par exemple, que l'on veuille écrire une méthode qui prend un objet immuable et, s'il n'a pas encore été mis en cache, l'exécuteToString
et met en cache le résultat; s'il a été mis en cache, renvoyez simplement la chaîne mise en cache. Ce n'est pas une chose déraisonnable à faire, mais cela échouerait gravementDecimal
car deux valeurs pourraient se comparer mais donner des chaînes différentes.La conjecture de Vilx est correcte. "CanCompareBits" vérifie si le type de valeur en question est "étroitement compressé" en mémoire. Une structure très compacte est comparée en comparant simplement les bits binaires qui composent la structure; une structure peu serrée est comparée en appelant Equals sur tous les membres.
Ceci explique l'observation de SLaks qu'elle reproche avec des structures qui sont toutes doubles; ces structures sont toujours bien emballées.
Malheureusement, comme nous l'avons vu ici, cela introduit une différence sémantique car la comparaison au niveau du bit des doubles et la comparaison égale des doubles donne des résultats différents.
la source
Une demi-réponse:
Reflector nous dit que cela
ValueType.Equals()
fait quelque chose comme ceci:Malheureusement, les deux
CanCompareBits()
etFastEquals()
(les deux méthodes statiques) sont extern ([MethodImpl(MethodImplOptions.InternalCall)]
) et n'ont aucune source disponible.Retour à deviner pourquoi un cas peut être comparé par bits, et l'autre pas (problèmes d'alignement peut-être?)
la source
Il ne donne vrai pour moi, avec les gmcs de Mono 2.4.2.3.
la source
Cas de test plus simple:
EDIT : Le bogue se produit également avec des flottants, mais ne se produit que si les champs de la structure totalisent un multiple de 8 octets.
la source
double
est0
. Vous vous trompez.Elle doit être liée à une comparaison bit par bit, car elle
0.0
ne doit différer-0.0
que du bit de signal.la source
Remplacez toujours Equals et GetHashCode sur les types de valeur. Ce sera rapide et correct.
la source
Juste une mise à jour pour ce bug de 10 ans: il a été corrigé ( Avertissement : je suis l'auteur de ce PR) dans .NET Core qui serait probablement publié dans .NET Core 2.1.0.
Le billet de blog a expliqué le bogue et comment je l'ai corrigé.
la source
Si vous faites D2 comme ça
c'est vrai.
si tu le fais comme ça
C'est toujours faux.
i t semble que c'est faux si le struct ne détient que double.
la source
Il doit être lié à zéro, car le changement de ligne
à:
résulte que la comparaison est vraie ...
la source