Dovremmo confrontare i numeri in virgola mobile per l’uguaglianza con un errore * relativo *?

Finora ho visto molti post riguardanti l’uguaglianza dei numeri in virgola mobile. La risposta standard a una domanda come “come dovremmo decidere se xey sono uguali?” è

abs(x - y) < epsilon 

dove epsilon è una costante fissa piccola. Questo perché gli “operandi” x e y sono spesso i risultati di alcuni calcoli in cui è coinvolto un errore di arrotondamento, quindi l’operatore di uguaglianza standard == non è ciò che intendiamo, e ciò che dovremmo veramente chiedere è se xey sono vicini , non uguale.

Ora, sento che se x è “quasi uguale” a y, allora anche x * 10 ^ 20 dovrebbe essere “quasi uguale” a y * 10 ^ 20, nel senso che l’errore relativo dovrebbe essere lo stesso (ma “relativo” “a cosa?). Ma con questi grandi numeri, il suddetto test fallirebbe, cioè quella soluzione non “scala”.

Come affronteresti questo problema? Dovremmo ridimensionare i numeri o ridimensionare epsilon? Come? (O la mia intuizione è sbagliata?)

Ecco una domanda correlata , ma non mi piace la sua risposta accettata, perché la cosa reinterpret_cast mi sembra un po ‘complicata, non capisco cosa sta succedendo. Si prega di provare a fornire un semplice test.

Tutto dipende dal dominio del problema specifico. Sì, l’utilizzo dell’errore relativo sarà più corretto nel caso generale, ma può essere significativamente meno efficiente poiché implica una divisione in virgola mobile aggiuntiva. Se conosci la scala approssimativa dei numeri nel tuo problema, è ansible utilizzare un errore assoluto.

Questa pagina descrive una serie di tecniche per confrontare i galleggianti. Passa anche su una serie di problemi importanti, come quelli con subnormali, infiniti e NaN. È un’ottima lettura, consiglio vivamente di leggerlo fino in fondo.

Come soluzione alternativa, perché non arrotondare o troncare i numeri e quindi fare un confronto diretto? Impostando in anticipo il numero di cifre significative, puoi essere certo della precisione all’interno di quel limite.

Il problema è che con numeri molto grandi, il confronto con epsilon fallirà.

Forse una soluzione migliore (ma più lenta) sarebbe usare la divisione, ad esempio:

 div(max(a, b), min(a, b)) < eps + 1 

Ora l '"errore" sarà relativo.

L’utilizzo dell’errore relativo non è meno grave dell’uso di errori assoluti, ma presenta problemi sottili per valori prossimi allo zero a causa di problemi di arrotondamento. Un algoritmo tutt’altro che perfetto, ma alquanto robusto, combina approcci di errore assoluto e relativo:

 boolean approxEqual(float a, float b, float absEps, float relEps) { // Absolute error check needed when comparing numbers near zero. float diff = abs(a - b); if (diff <= absEps) { return true; } // Symmetric relative error check without division. return (diff <= relEps * max(abs(a), abs(b))); } 

Ho adattato questo codice dall'eccellente articolo di Bruce Dawson Confrontando i numeri in virgola mobile, edizione 2012 , una lettura obbligata per chiunque faccia confronti a virgola mobile - un argomento incredibilmente complesso con molte insidie.

La maggior parte delle volte in cui il codice confronta i valori, lo fa per rispondere ad una sorta di domanda. Per esempio:

  1. Se so che cosa è restituita una funzione quando viene assegnato un valore di X, posso supporre che restituirà la stessa cosa se viene data Y?

  2. Se ho un metodo per calcolare una funzione che è lenta ma accurata, sono disposto ad accettare alcune imprecisioni in cambio di velocità, e voglio testare una funzione candidata che sembra adattarsi alla fattura, sono gli output di quella funzione abbastanza vicini a quello noto preciso per essere considerato “corretto”.

Per rispondere alla prima domanda, il codice dovrebbe idealmente fare un confronto bit-saggio sul valore, anche se a meno che un linguaggio supporti i nuovi operatori aggiunti a IEEE-754 nel 2009 che potrebbero essere meno efficienti dell’ideale. Per rispondere alla seconda domanda, è necessario definire quale grado di accuratezza è richiesto e testarlo.

Non penso che ci sia molto merito in un metodo generale che considera come cose uguali che sono vicine, poiché le diverse applicazioni avranno requisiti diversi per la tolleranza assoluta e relativa, sulla base di quali domande esatte dovrebbero rispondere i test.