Porównując wartości zmiennoprzecinkowe dla równości, istnieją dwa różne podejścia:
NaN
nie są sobie równe, co odpowiada specyfikacji IEEE 754 .NaN
będąc równym sobie, co zapewnia matematyczną właściwość Refleksyjności, która jest niezbędna do zdefiniowania relacji Równoważności
Wbudowane typy zmiennoprzecinkowe IEEE w języku C # ( float
i double
) są zgodne z semantyką IEEE dla ==
i !=
(oraz operatorów relacyjnych, takich jak <
), ale zapewniają zwrotność dla object.Equals
, IEquatable<T>.Equals
(i CompareTo
).
Rozważmy teraz bibliotekę, która zawiera struktury wektorowe na float
/ double
. Taki typ wektora spowodowałby przeciążenie ==
/ !=
i zastąpienie object.Equals
/ IEquatable<T>.Equals
.
Co każdy zgadza się na to, że ==
/ !=
powinny być zgodne IEEE semantykę. Pytanie brzmi, czy taka biblioteka powinna implementować Equals
metodę (która jest niezależna od operatorów równości) w sposób zwrotny lub zgodny z semantyką IEEE.
Argumenty za zastosowaniem semantyki IEEE dla Equals
:
- Jest zgodny z IEEE 754
Jest (prawdopodobnie znacznie) szybszy, ponieważ może korzystać z instrukcji SIMD
Zadałem osobne pytanie na temat przepełnienia stosu dotyczące sposobu wyrażenia równości zwrotnej za pomocą instrukcji SIMD i ich wpływu na wydajność: instrukcje SIMD do porównania zmiennoprzecinkowego
Aktualizacja: Wydaje się, że możliwe jest skuteczne wdrożenie równości zwrotnej przy użyciu trzech instrukcji SIMD.
Dokumentacja dotycząca
Equals
nie wymaga zwrotności w przypadku zmiennoprzecinkowego:Poniższe instrukcje muszą być prawdziwe dla wszystkich implementacji metody Equals (Object). Na liście
x
,y
iz
reprezentują odwołań do obiektów, które nie są puste.x.Equals(x)
zwracatrue
, z wyjątkiem przypadków, które dotyczą typów zmiennoprzecinkowych. Patrz ISO / IEC / IEEE 60559: 2011, Informatyka - Systemy mikroprocesorowe - Arytmetyka zmiennoprzecinkowa.Jeśli używasz liczb zmiennoprzecinkowych jako kluczy słownikowych, żyjesz w stanie grzechu i nie powinieneś oczekiwać rozsądnego zachowania.
Argumenty za byciem zwrotnym:
Jest to zgodne z istniejących typów, w tym
Single
,Double
,Tuple
iSystem.Numerics.Complex
.Nie znam żadnego precedensu w BCL, w którym
Equals
następuje IEEE zamiast bycia refleksyjnym. Licznik przykłady obejmująSingle
,Double
,Tuple
iSystem.Numerics.Complex
.Equals
jest najczęściej używany przez kontenery i algorytmy wyszukiwania, które opierają się na zwrotności. W przypadku tych algorytmów wzrost wydajności nie ma znaczenia, jeśli uniemożliwia im działanie. Nie poświęcajcie poprawności dla wydajności.- Łamie ona wszystkie zestawy oparte hash i słowniki,
Contains
,Find
,IndexOf
na różnych zbiorach / LINQ, zestaw operacji na bazie (LINQUnion
,Except
itp), jeśli dane zawierająNaN
wartości. Kod wykonujący rzeczywiste obliczenia, w których semantyczny IEEE jest akceptowalny, zwykle działa na konkretnych typach i wykorzystuje
==
/!=
(lub bardziej prawdopodobne porównania epsilon).Obecnie nie można pisać obliczeń o wysokiej wydajności przy użyciu ogólnych, ponieważ potrzebne są do tego operacje arytmetyczne, ale nie są one dostępne za pośrednictwem interfejsów / metod wirtualnych.
Dlatego wolniejsza
Equals
metoda nie wpłynie na większość kodu o wysokiej wydajności.Możliwe jest podanie
IeeeEquals
metody lubIeeeEqualityComparer<T>
dla przypadków, w których albo potrzebujesz semantyki IEEE, albo potrzebujesz przewagi wydajności.
Moim zdaniem argumenty te zdecydowanie sprzyjają refleksyjnej realizacji.
Zespół Microsoft CoreFX planuje wprowadzić taki typ wektora w .NET. W przeciwieństwie do mnie preferują rozwiązanie IEEE , głównie ze względu na zalety wydajnościowe. Ponieważ taka decyzja z pewnością nie ulegnie zmianie po ostatecznym wydaniu, chcę uzyskać informacje zwrotne od społeczności na temat tego, co uważam za duży błąd.
float
/ double
i kilku innych typów ==
i Equals
już są różne. Myślę, że niespójność z istniejącymi typami byłaby nawet bardziej myląca niż niespójność między ==
i Equals
nadal będziesz musiał radzić sobie z innymi typami. 2) Prawie wszystkie ogólne algorytmy / kolekcje używają Equals
i polegają na swojej zwrotności do działania (LINQ i słowniki), podczas gdy konkretne algorytmy zmiennoprzecinkowe zwykle używają ==
tam, gdzie uzyskują semantykę IEEE.
Vector<float>
inną „bestię” niż zwykłą float
lub double
. Dzięki takiemu rozwiązaniu nie widzę powodu Equals
ani ==
operatora do przestrzegania ich standardów. Sam powiedziałeś: „Jeśli używasz liczb zmiennoprzecinkowych jako kluczy słownikowych, żyjesz w stanie grzechu i nie powinieneś oczekiwać rozsądnego zachowania”. Jeśli ktoś miałby przechowywać NaN
w słowniku, to ich cholerna wina za stosowanie okropnej praktyki. Nie sądzę, żeby zespół CoreFX nie przemyślał tego. Wybrałbym coś ReflexiveEquals
podobnego, tylko ze względu na wydajność.
==
iEquals
zwróciłoby inne wyniki. Wielu programistów zakłada, że są i robią to samo . Ponadto - ogólnie, implementacje operatorów równości przywołują tęEquals
metodę. Argumentowałeś, że można dołączyć aIeeeEquals
, ale można to zrobić na odwrót i dołączyć metodęReflexiveEquals
. TenVector<float>
typ może być używany w wielu aplikacjach krytycznych pod względem wydajności i powinien zostać odpowiednio zoptymalizowany.