Beim Vergleich von Gleitkommawerten auf Gleichheit gibt es zwei verschiedene Ansätze:
NaN
nicht gleich sich selbst sein, was der IEEE 754- Spezifikation entspricht.NaN
Gleichheit mit sich selbst, die die mathematische Eigenschaft der Reflexivität liefert, die für die Definition einer Äquivalenzbeziehung wesentlich ist
Die eingebauten IEEE-Gleitkommatypen in C # ( float
und double
) folgen der IEEE-Semantik für ==
und !=
(und den relationalen Operatoren wie <
), gewährleisten jedoch die Reflexivität für object.Equals
, IEquatable<T>.Equals
(und CompareTo
).
Betrachten Sie nun eine Bibliothek, die Vektorstrukturen über float
/ bereitstellt double
. Ein solcher Vektortyp würde ==
/ !=
und object.Equals
/ überschreiben IEquatable<T>.Equals
.
Alle sind sich einig, dass ==
/ !=
sollte der IEEE-Semantik folgen. Die Frage ist, Equals
ob eine solche Bibliothek die Methode (die von den Gleichheitsoperatoren getrennt ist) auf eine Weise implementiert , die reflexiv ist oder der IEEE-Semantik entspricht.
Argumente für die Verwendung der IEEE-Semantik für Equals
:
- Es folgt IEEE 754
Es ist (möglicherweise viel) schneller, weil es SIMD-Anweisungen nutzen kann
Ich habe eine separate Frage zum Stapelüberlauf gestellt, wie Sie die reflexive Gleichheit mithilfe von SIMD-Anweisungen und deren Auswirkungen auf die Leistung ausdrücken würden: SIMD-Anweisungen für den Gleitkomma-Gleichheitsvergleich
Update: Es scheint möglich zu sein, die reflexive Gleichheit mithilfe von drei SIMD-Anweisungen effizient zu implementieren.
Die Dokumentation für
Equals
erfordert keine Reflexivität, wenn Gleitkomma verwendet wird:Die folgenden Aussagen müssen für alle Implementierungen der Equals (Object) -Methode zutreffen. In der Liste
x
,y
undz
Objektreferenzen darstellen , die nicht null sind.x.Equals(x)
Gibt zurücktrue
, außer in Fällen, in denen Gleitkommatypen verwendet werden. Siehe ISO / IEC / IEEE 60559: 2011, Informationstechnologie - Mikroprozessorsysteme - Gleitkomma-Arithmetik.Wenn Sie Floats als Wörterbuchschlüssel verwenden, leben Sie in einem Zustand der Sünde und sollten kein vernünftiges Verhalten erwarten.
Argumente für Reflexivität:
Es ist im Einklang mit dem bestehenden Typen, einschließlich
Single
,Double
,Tuple
undSystem.Numerics.Complex
.Ich kenne keinen Präzedenzfall in der BCL, in
Equals
dem IEEE folgt, anstatt reflexiv zu sein. Gegenbeispiele sindSingle
,Double
,Tuple
undSystem.Numerics.Complex
.Equals
wird hauptsächlich von Containern und Suchalgorithmen verwendet, die auf Reflexivität beruhen. Für diese Algorithmen ist ein Leistungsgewinn irrelevant, wenn sie nicht funktionieren. Opfern Sie nicht die Korrektheit für die Leistung.- Es bricht alle Hash - basierte Sets und Wörterbücher,
Contains
,Find
,IndexOf
auf verschiedenen Sammlungen / LINQ, Satz basiert LINQ - Operationen (Union
,Except
usw.) , wenn die Daten enthältNaN
Werte. Code, der tatsächliche Berechnungen durchführt, bei denen die IEEE-Semantik akzeptabel ist, funktioniert normalerweise mit konkreten Typen und Verwendungen
==
/!=
(oder wahrscheinlicher Epsilon-Vergleichen).Sie können derzeit keine Hochleistungsberechnungen mit Generika schreiben, da Sie dafür arithmetische Operationen benötigen, diese sind jedoch nicht über Schnittstellen / virtuelle Methoden verfügbar.
Eine langsamere
Equals
Methode würde sich also nicht auf die meisten Hochleistungscodes auswirken.Es ist möglich, eine
IeeeEquals
Methode oder eineIeeeEqualityComparer<T>
für die Fälle bereitzustellen, in denen Sie entweder die IEEE-Semantik oder einen Leistungsvorteil benötigen.
Meiner Meinung nach sprechen diese Argumente stark für eine reflexive Umsetzung.
Das CoreFX-Team von Microsoft plant die Einführung eines solchen Vektortyps in .NET. Im Gegensatz zu mir bevorzugen sie die IEEE-Lösung , hauptsächlich aufgrund der Leistungsvorteile. Da eine solche Entscheidung nach einer endgültigen Veröffentlichung sicherlich nicht geändert wird, möchte ich Feedback von der Community erhalten, was ich für einen großen Fehler halte.
quelle
==
undEquals
würde unterschiedliche Ergebnisse liefern. Viele Programmierer gehen davon aus und tun dasselbe . Darüber hinaus rufen Implementierungen der Gleichheitsoperatoren im Allgemeinen dieEquals
Methode auf. Sie haben argumentiert, dass man eine einschließen könnteIeeeEquals
, aber man könnte es auch umgekehrt machen und eine Methode einschließenReflexiveEquals
. DerVector<float>
Typ kann in vielen leistungskritischen Anwendungen verwendet werden und sollte entsprechend optimiert werden.float
/double
und einige andere Typen==
undEquals
sind bereits unterschiedlich. Ich denke, eine Inkonsistenz mit vorhandenen Typen wäre noch verwirrender als die Inkonsistenz zwischen==
undEquals
Sie müssen sich immer noch mit anderen Typen befassen. 2) Nahezu alle generischen Algorithmen / Sammlungen verwendenEquals
und verlassen sich auf ihre Funktionsreflexivität (LINQ und Wörterbücher), während konkrete Gleitkomma-Algorithmen normalerweise dort verwendet werden,==
wo sie ihre IEEE-Semantik erhalten.Vector<float>
ein anderes "Biest" in Betracht ziehen als ein einfachesfloat
oderdouble
. Durch diese Maßnahme kann ich den GrundEquals
oder den==
Betreiber nicht erkennen , die Standards von ihnen einzuhalten. Sie sagten selbst: "Wenn Sie Floats als Wörterbuchschlüssel verwenden, leben Sie in einem Zustand der Sünde und sollten kein vernünftiges Verhalten erwarten." Wenn manNaN
in einem Wörterbuch speichert , dann ist es ihre eigene verdammte Schuld, schreckliche Praktiken anzuwenden. Ich denke kaum, dass das CoreFX-Team dies nicht durchdacht hat. Ich würde mit einemReflexiveEquals
oder ähnlichem gehen, nur um der Leistung willen.Antworten:
Ich würde argumentieren, dass das IEEE-Verhalten korrekt ist.
NaN
s sind in keiner Weise gleichwertig; Sie entsprechen schlecht definierten Bedingungen, unter denen eine numerische Antwort nicht angemessen ist.Abgesehen von den Leistungsvorteilen, die sich aus der Verwendung der IEEE-Arithmetik ergeben, die die meisten Prozessoren nativ unterstützen, gibt es meines Erachtens ein semantisches Problem, wenn dies gesagt
isnan(x) && isnan(y)
wirdx == y
. Zum Beispiel:Ich würde argumentieren, dass es keinen sinnvollen Grund gibt, warum man dies für
x
gleich halten würdey
. Man kann kaum schlussfolgern, dass es sich um äquivalente Zahlen handelt; Sie sind überhaupt keine Zahlen, daher scheint es nur ein völlig ungültiges Konzept zu sein.Aus Sicht des API-Designs ist es außerdem sinnvoll, die branchenweit typischste Gleitkommasemantik zu verwenden, wenn Sie an einer Allzweckbibliothek arbeiten, die von vielen Programmierern verwendet werden soll. Das Ziel einer guten Bibliothek ist es, Zeit für diejenigen zu sparen, die sie nutzen. Daher ist das Einbauen von nicht standardisiertem Verhalten reif für Verwirrung.
quelle
NaN == NaN
sollte false zurückgeben ist unbestritten. Die Frage ist, was die.Equals
Methode tun soll. Wenn ichNaN
beispielsweise als Wörterbuchschlüssel verwende, kann der zugehörige Wert nicht mehr abgerufen werden, wennNaN.Equals(NaN)
false zurückgegeben wird.Single
,Double
usw. Klassen bereits die reflexiven Verhalten haben. IMHO, das war zunächst nur die falsche Entscheidung. Aber ich würde nicht zulassen, dass Eleganz der Nützlichkeit / Geschwindigkeit im Wege steht.==
die immer IEEE gefolgt sind, sodass sie den schnellen Code erhalten, unabhängig davon, wie erEquals
implementiert ist. IMO ist der springende Punkt bei der Verwendung einer separatenEquals
Methode die Verwendung in Algorithmen, die sich nicht um den konkreten Typ kümmern, wie z. B. dieDistinct()
Funktion von LINQ .==
Operator und eineEquals()
Funktion hat, die unterschiedliche Semantik haben. Ich denke, Sie zahlen aus Entwicklersicht die Kosten potenzieller Verwirrung ohne wirklichen Nutzen (ich lege keinen Wert darauf zu, einen Zahlenvektor als Wörterbuchschlüssel verwenden zu können). Es ist nur meine Meinung; Ich glaube nicht, dass es eine objektive Antwort auf die vorliegende Frage gibt.Es gibt ein Problem: IEEE754 definiert relationale Operationen und Gleichheit auf eine Weise, die für numerische Anwendungen gut geeignet ist. Es ist nicht gut zum Sortieren und Hashing geeignet. Wenn Sie also ein Array nach numerischen Werten sortieren oder einem Satz numerische Werte hinzufügen oder diese als Schlüssel in einem Wörterbuch verwenden möchten, erklären Sie entweder, dass NaN-Werte nicht zulässig sind, oder Sie verwenden IEEE754 nicht eingebaute Operationen. Ihre Hash-Tabelle müsste sicherstellen, dass alle NaNs mit demselben Wert übereinstimmen, und gleich miteinander vergleichen.
Wenn Sie Vektor definieren, müssen Sie die Entwurfsentscheidung treffen, ob Sie ihn nur für numerische Zwecke verwenden möchten oder ob er mit Sortieren und Hashing kompatibel sein soll. Ich persönlich denke, dass der numerische Zweck viel wichtiger sein sollte. Wenn Sortieren / Hashing erforderlich ist, können Sie eine Klasse mit Vector als Mitglied schreiben und Hashing und Gleichheit in dieser Klasse nach Ihren Wünschen definieren.
quelle
==
und!=
Operatoren für sie. Nach meiner Erfahrung wird dieEquals
Methode so ziemlich nur von nicht numerischen Algorithmen verwendet.