`Vector <float> .Equals` dovrebbe essere riflessivo o dovrebbe seguire la semantica IEEE 754?


9

Quando si confrontano valori in virgola mobile per l'uguaglianza, esistono due approcci diversi:

  • NaNnon essere uguale a se stesso, che corrisponde alla specifica IEEE 754 .
  • NaNessendo uguale a se stesso, che fornisce la proprietà matematica della Riflessività che è essenziale per la definizione di una relazione di equivalenza

I tipi in virgola mobile IEEE integrati in C # ( floate double) seguono la semantica IEEE per ==e !=(e gli operatori relazionali gradiscono <) ma assicurano la riflessività per object.Equals, IEquatable<T>.Equals(e CompareTo).

Consideriamo ora una libreria che fornisce strutture vettoriali in cima a float/ double. Un tale tipo di vettore sovraccaricherebbe ==/ !=e sovrascriverà object.Equals/ IEquatable<T>.Equals.

Ciò su cui tutti concordano è che ==/ !=dovrebbero seguire la semantica IEEE. La domanda è: se tale libreria implementasse il Equalsmetodo (che è separato dagli operatori di uguaglianza) in un modo che è riflessivo o in un modo che corrisponde alla semantica IEEE.

Argomenti per l'utilizzo della semantica IEEE per Equals:

  • Segue IEEE 754
  • È (forse molto) più veloce perché può trarre vantaggio dalle istruzioni SIMD

    Ho fatto una domanda separata 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 in modo efficiente l'uguaglianza riflessiva usando tre istruzioni SIMD.

  • La documentazione per Equalsnon richiede riflessività quando si tratta di virgola mobile:

    Le seguenti istruzioni devono essere vere per tutte le implementazioni del metodo Equals (Object). Nell'elenco, x, y, e zrappresentano riferimenti a oggetti che non sono nulla.

    x.Equals(x)restituisce true, tranne nei casi che coinvolgono tipi in virgola mobile. Vedi ISO / IEC / IEEE 60559: 2011, Tecnologia dell'informazione - Sistemi a microprocessore - Aritmetica a virgola mobile.

  • Se stai usando i float come chiavi del dizionario, stai vivendo in uno stato di peccato e non dovresti aspettarti un comportamento sano.

Argomenti per essere riflessivo:

  • E 'coerente con i tipi esistenti, tra cui Single, Double, Tuplee System.Numerics.Complex.

    Non conosco alcun precedente nel BCL in cui Equalssegue IEEE invece di essere riflessivo. Esempi includono contatori Single, Double, Tuplee System.Numerics.Complex.

  • Equalsviene 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.
  • Si rompe tutti i set e dizionari hash basato, Contains, Find, IndexOfsu varie collezioni / LINQ, le operazioni di set LINQ base ( Union, Except, ecc) se i dati contengono NaNvalori.
  • Il codice che esegue calcoli effettivi in ​​cui è accettabile la semantica IEEE di solito funziona su tipi e usi concreti ==/ !=(o più probabilmente confronti epsilon).

    Al momento non è possibile scrivere calcoli ad alte prestazioni utilizzando i generici poiché sono necessarie operazioni aritmetiche, ma questi non sono disponibili tramite interfacce / metodi virtuali.

    Quindi un Equalsmetodo più lento non influirebbe sulla maggior parte del codice ad alte prestazioni.

  • È possibile fornire un IeeeEqualsmetodo o uno IeeeEqualityComparer<T>per i casi in cui è necessaria la semantica IEEE o è necessario un vantaggio in termini di prestazioni.

A mio avviso, questi argomenti favoriscono fortemente un'implementazione riflessiva.

Il team CoreFX di Microsoft prevede di introdurre un tale tipo di vettore in .NET. A differenza di me , preferiscono la soluzione IEEE , principalmente a causa dei vantaggi prestazionali. Dal momento che una tale decisione non sarà certamente cambiata dopo una versione finale, voglio ottenere un feedback dalla community, su quello che credo sia un grosso errore.


1
Domanda eccellente e stimolante. Per me (almeno), non ha senso ==e Equalsrestituirebbe risultati diversi. Molti programmatori credono di si e fanno la stessa cosa . Inoltre - in generale, le implementazioni degli operatori di uguaglianza invocano il Equalsmetodo. Hai sostenuto che si potrebbe includere un IeeeEquals, ma si potrebbe anche fare il contrario e includere un ReflexiveEqualsmetodo. Il Vector<float>tipo può essere utilizzato in molte applicazioni critiche per le prestazioni e deve essere ottimizzato di conseguenza.
die maus,

@diemaus Alcuni motivi per cui non lo trovo convincente: 1) per float/ doublee molti altri tipi, ==e Equalssono già diversi. Penso che un'incongruenza con i tipi esistenti sarebbe ancora più confusa rispetto all'incongruenza tra ==e Equalsdovrai comunque occuparti di altri tipi. 2) Praticamente tutti gli algoritmi / raccolte generici usano Equalse si basano sulla sua riflessività per funzionare (LINQ e dizionari), mentre gli algoritmi concreti in virgola mobile usano tipicamente ==dove ottengono la loro semantica IEEE.
CodesInChaos,

Vorrei considerare Vector<float>una "bestia" diversa da una semplice floato double. Con questa misura, non riesco a vedere il motivo Equalso l' ==operatore di rispettarne gli standard. Ti sei detto: "Se stai usando i float come chiavi del dizionario, stai vivendo in uno stato di peccato e non dovresti aspettarti un comportamento sano". Se uno dovesse archiviare NaNin un dizionario, allora è colpa sua maledetta per l'uso della pratica terribile. Non credo che il team CoreFX non ci abbia pensato. Vorrei andare con un ReflexiveEqualso simile, solo per motivi di prestazioni.
die maus,

Risposte:


5

Direi che il comportamento IEEE è corretto. NaNs non sono equivalenti tra loro in alcun modo; corrispondono a condizioni mal definite in cui una risposta numerica non è appropriata.

Al di là dei vantaggi in termini di prestazioni derivanti dall'utilizzo dell'aritmetica IEEE che la maggior parte dei processori supporta in modo nativo, penso che ci sia un problema semantico nel dire che se isnan(x) && isnan(y), allora x == y. Per esempio:

// C++
double inf = std::numeric_limits<double>::infinity();
double x = 0.0 / 0.0;
double y = inf - inf;

Direi che non esiste un motivo significativo per cui uno dovrebbe considerare xuguale y. Difficilmente si può concludere che si tratta di numeri equivalenti; non sono affatto numeri, quindi sembra solo un concetto non valido.

Inoltre, dal punto di vista della progettazione API, se stai lavorando su una libreria per scopi generici che è destinata a essere utilizzata da molti programmatori, ha senso utilizzare la semantica a virgola mobile più tipica del settore. L'obiettivo di una buona biblioteca è quello di risparmiare tempo per coloro che la usano, quindi costruire un comportamento non standard è maturo per la confusione.


3
Ciò NaN == NaNdovrebbe restituire false è indiscusso. La domanda è: cosa .Equalsdovrebbe fare il metodo. Ad esempio, se uso NaNcome chiave del dizionario, il valore associato diventa irrecuperabile se NaN.Equals(NaN)restituisce false.
CodesInChaos,

1
Penso che devi ottimizzare per il caso comune. Il caso comune di un vettore di numeri è il calcolo numerico ad alto rendimento (spesso ottimizzato con le istruzioni SIMD). Direi che usare un vettore come chiave di dizionario è un caso d'uso estremamente raro, e difficilmente vale la pena progettare la tua semantica. La confutazione che sembra più ragionevole per me è la consistenza, dal momento che le esistenti Single, Doubleclassi, ecc hanno già il comportamento riflessiva. IMHO, è stata la decisione sbagliata per cominciare. Ma non lascerei che l'eleganza ostacoli l'utilità / la velocità.
Jason R,

Ma in genere verranno utilizzati calcoli numerici ==che hanno sempre seguito IEEE, quindi otterrebbero il codice veloce in qualsiasi modoEquals viene implementato. In IMO il punto fondamentale di avere un Equalsmetodo separato è usare in algoritmi a cui non interessa il tipo concreto, come la Distinct()funzione di LINQ .
CodesInChaos

1
Capisco quello. Ma vorrei discutere contro un'API che ha un ==operatore e una Equals()funzione con semantica diversa. Penso che tu stia pagando un costo di potenziale confusione dal punto di vista dello sviluppatore, senza alcun vantaggio reale (non attribuisco alcun valore alla possibilità di utilizzare un vettore di numeri come chiave del dizionario). È solo la mia opinione; Non credo che ci sia una risposta obiettiva alla domanda in corso.
Jason R,

0

C'è un problema: IEEE754 definisce le operazioni relazionali e l'uguaglianza in un modo che ben si adatta alle applicazioni numeriche. Non è adatto per l'ordinamento e l'hashing. Pertanto, se si desidera ordinare un array in base a valori numerici o se si desidera aggiungere valori numerici a un set o utilizzarli come chiavi in ​​un dizionario, si dichiara che i valori NaN non sono consentiti o non si utilizza IEEE754 operazioni integrate. La tua tabella hash dovrebbe assicurarsi che tutte le NaN siano abbinate allo stesso valore e confrontate uguali tra loro.

Se si definisce Vector, è necessario prendere la decisione di progettazione se si desidera utilizzarlo solo a scopo numerico o se deve essere compatibile con l'ordinamento e l'hashing. Personalmente penso che lo scopo numerico dovrebbe essere molto più importante. Se è necessario l'ordinamento / hashing, puoi scrivere una classe con Vector come membro e definire hashing e uguaglianza in quella classe nel modo che preferisci.


1
Sono d'accordo che gli scopi numerici sono più importanti. Ma abbiamo già gli operatori ==e !=per loro. Nella mia esperienza il Equalsmetodo è praticamente usato solo da algoritmi non numerici.
CodesInChaos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.