Hier ist das Beispiel mit Kommentaren:
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
}
}
Sag mal, wie findest du das?
c#
.net
floating-point
Alexander Efimov
quelle
quelle
c.d.Equals(d.d)
auswertet , umtrue
ebenso wiec.f.Equals(d.f)
Antworten:
Der Fehler ist in den folgenden zwei Zeilen von
System.ValueType
: (Ich trat in die Referenzquelle)(Beide Methoden sind
[MethodImpl(MethodImplOptions.InternalCall)]
)Wenn alle Felder 8 Byte breit sind, wird
CanCompareBits
fälschlicherweise true zurückgegeben, was zu einem bitweisen Vergleich zweier verschiedener, aber semantisch identischer Werte führt.Wenn mindestens ein Feld nicht 8 Byte breit ist, wird
CanCompareBits
false zurückgegeben, und der Code verwendet die Reflexion, um die Felder zu durchlaufen undEquals
jeden Wert aufzurufen , der korrekt-0.0
als gleich behandelt wird0.0
.Hier ist die Quelle für
CanCompareBits
SSCLI:quelle
IsNotTightlyPacked
.The bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
Ich fand die Antwort unter http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .
Das Kernstück ist der Quellkommentar, anhand
CanCompareBits
dessenValueType.Equals
bestimmt wird, ob einmemcmp
Stilvergleich verwendet werden soll:Der Autor führt das vom OP beschriebene Problem genau an:
quelle
Equals(Object)
fordouble
,float
undDecimal
während der frühen Entwürfe von .net geändert hat. Ich würde denken , dass es wichtiger ist , die virtuelle habenX.Equals((Object)Y)
nur Rückkehr ,true
wennX
undY
sind nicht zu unterscheiden, als das Verfahren zu haben , das Verhalten anderer Überlastungen zu entsprechen (vor allem da, wegen der impliziten Typumwandlung, überladeneEquals
Methoden definieren nicht einmal eine Äquivalenzbeziehung !, zB1.0f.Equals(1.0)
ergibt falsch, aber1.0.Equals(1.0f)
ergibt wahr!) Das eigentliche Problem istEquals
, um etwas anderes als Äquivalenz zu bedeuten. Angenommen, man möchte beispielsweise eine Methode schreiben, die ein unveränderliches Objekt nimmt und, falls es noch nicht zwischengespeichert wurde, es ausführtToString
und das Ergebnis zwischenspeichert. Wenn es zwischengespeichert wurde, geben Sie einfach die zwischengespeicherte Zeichenfolge zurück. Keine unvernünftige Sache, aber es würde schlimm scheitern,Decimal
da zwei Werte gleich sind, aber unterschiedliche Zeichenfolgen ergeben.Vilx 'Vermutung ist richtig. "CanCompareBits" prüft, ob der betreffende Werttyp im Speicher "dicht gepackt" ist. Eine dicht gepackte Struktur wird verglichen, indem einfach die Binärbits verglichen werden, aus denen die Struktur besteht. Eine lose gepackte Struktur wird verglichen, indem für alle Mitglieder Equals aufgerufen wird.
Dies erklärt die Beobachtung von SLaks, dass es sich um Strukturen handelt, die alle doppelt sind. solche Strukturen sind immer dicht gepackt.
Wie wir hier gesehen haben, führt dies leider zu einem semantischen Unterschied, da der bitweise Vergleich von Doppelwerten und der Gleichheitsvergleich von Doppelwerten unterschiedliche Ergebnisse liefert.
quelle
Eine halbe Antwort:
Der Reflektor sagt uns, dass er
ValueType.Equals()
so etwas macht:Leider sind beide
CanCompareBits()
undFastEquals()
(beide statische Methoden) extern ([MethodImpl(MethodImplOptions.InternalCall)]
) und haben keine Quelle zur Verfügung.Zurück zur Vermutung, warum ein Fall mit Bits verglichen werden kann und der andere nicht (Ausrichtungsprobleme vielleicht?)
quelle
Es wird geben gilt für mich, mit Monos gmcs 2.4.2.3.
quelle
Einfacherer Testfall:
BEARBEITEN : Der Fehler tritt auch bei Floats auf, jedoch nur, wenn die Felder in der Struktur ein Vielfaches von 8 Bytes ergeben.
quelle
double
ist0
. Du liegst falsch.Es muss sich auf einen bitweisen Vergleich beziehen, da
0.0
sich dieser-0.0
nur durch das Signalbit unterscheiden sollte .quelle
Überschreiben Sie bei Werttypen immer Equals und GetHashCode. Es wird schnell und korrekt sein.
quelle
Nur ein Update für diesen 10 Jahre alten Fehler: Er wurde in .NET Core behoben ( Haftungsausschluss : Ich bin der Autor dieser PR) und wahrscheinlich in .NET Core 2.1.0 veröffentlicht.
Der Blog-Beitrag erklärte den Fehler und wie ich ihn behoben habe.
quelle
Wenn du D2 so machst
es ist wahr.
wenn du es so machst
Es ist immer noch falsch.
Es scheint falsch zu sein, wenn die Struktur nur Doubles enthält.
quelle
Es muss nullbezogen sein, da die Zeile geändert wird
zu:
führt dazu, dass der Vergleich wahr ist ...
quelle