Quando si confrontano i valori in virgola mobile per l'uguaglianza, ci sono due approcci diversi:
-
NaNnon è 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
Equalsnon 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, andzrepresent 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,TupleeSystem.Numerics.Complex.Non conosco alcun precedente nel BCL in cui
Equalssegue IEEE invece di essere riflessivo. I contro esempi includonoSingle,Double,TupleeSystem.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,IndexOfsu 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
Equalspiù lento non influisce sulla maggior parte del codice ad alte prestazioni. -
È possibile fornire un metodo
IeeeEqualsoIeeeEqualityComparer<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.