Quando si confrontano i valori in virgola mobile per l'uguaglianza, ci sono due approcci diversi:
-
NaN
non è uguale a se stesso, che corrisponde alla specifica IEEE 754 . -
NaN
è uguale a se stesso, che fornisce la proprietà matematica di Reflexivity che è essenziale per la definizione di Relazione di equivalenza
I tipi di virgola mobile IEEE incorporati in C # ( float
e double
) seguono la semantica IEEE per ==
e !=
(e gli operatori relazionali come <
) ma assicurano la riflessività per object.Equals
, IEquatable<T>.Equals
(e CompareTo
).
Ora considera una libreria che fornisce strutture vettoriali su float
/ double
. Un tipo di vettore di questo tipo sovraccaricherebbe ==
/ !=
e sostituirà object.Equals
/ IEquatable<T>.Equals
.
Ciò su cui tutti sono d'accordo è che ==
/ !=
dovrebbe seguire la semantica IEEE. La domanda è, se una tale libreria implementasse il metodo Equals
(che è separato dagli operatori di uguaglianza) in un modo che è riflessivo o in un modo che corrisponda alla semantica IEEE.
Argomenti per l'utilizzo della semantica IEEE per Equals
:
- Segue IEEE 754
-
È (possibilmente molto) più veloce perché può trarre vantaggio dalle istruzioni SIMD
Ho fatto una domanda a parte su StackOverflow su come esprimere l'uguaglianza riflessiva usando le istruzioni SIMD e il loro impatto sulle prestazioni: Istruzioni SIMD per il confronto dell'uguaglianza in virgola mobile
Aggiornamento: Sembra che sia possibile implementare l'uguaglianza riflessiva in modo efficiente utilizzando tre istruzioni SIMD.
-
La documentazione di
Equals
non richiede riflessività quando si utilizza il floating point:The following statements must be true for all implementations of the Equals(Object) method. In the list,
x
,y
, andz
represent object references that are not null.x.Equals(x)
returnstrue
, except in cases that involve floating-point types. See ISO/IEC/IEEE 60559:2011, Information technology -- Microprocessor Systems -- Floating-Point arithmetic. -
Se utilizzi i float come chiavi del dizionario, stai vivendo in uno stato di peccato e non dovresti aspettarti un comportamento corretto.
Argomenti per essere riflessivi:
-
È coerente con i tipi esistenti, inclusi
Single
,Double
,Tuple
eSystem.Numerics.Complex
.Non conosco alcun precedente nel BCL in cui
Equals
segue IEEE invece di essere riflessivo. I contro esempi includonoSingle
,Double
,Tuple
eSystem.Numerics.Complex
. -
Equals
è utilizzato principalmente da contenitori e algoritmi di ricerca che si basano sulla riflessività. Per questi algoritmi un guadagno in termini di prestazioni è irrilevante se impedisce loro di funzionare. Non sacrificare la correttezza per le prestazioni. - Rompe tutti i set e i dizionari basati su hash,
Contains
,Find
,IndexOf
su varie raccolte / LINQ, imposta operazioni LINQ basate (Union
,Except
, ecc.) se i dati contengono% valori diNaN
. -
Il codice che esegue calcoli effettivi in cui la semantica IEEE è accettabile di solito funziona su tipi concreti e utilizza
==
/!=
(o più probabilmente confronti epsilon).Attualmente non puoi scrivere calcoli ad alte prestazioni usando i generici poiché hai bisogno di operazioni aritmetiche per questo, ma questi non sono disponibili attraverso interfacce / metodi virtuali.
Quindi un metodo
Equals
più lento non influisce sulla maggior parte del codice ad alte prestazioni. -
È possibile fornire un metodo
IeeeEquals
oIeeeEqualityComparer<T>
per i casi in cui è necessaria la semantica IEEE o è necessario un vantaggio in termini di prestazioni.
A mio parere questi argomenti strongmente favoriscono un'implementazione riflessiva.
Il team di CoreFX di Microsoft prevede di introdurre un tipo di vettore di questo tipo in .NET. A differenza di me preferiscono la soluzione IEEE , principalmente a causa dei vantaggi prestazionali. Dal momento che una tale decisione non sarà sicuramente cambiata dopo un rilascio finale, voglio ottenere un feedback dalla comunità, su quello che ritengo sia un grosso errore.