Confronta doppio a zero usando epsilon

Oggi stavo guardando un codice C ++ (scritto da qualcun altro) e ho trovato questa sezione:

double someValue = ... if (someValue < std::numeric_limits::epsilon() && someValue > -std::numeric_limits::epsilon()) { someValue = 0.0; } 

Sto cercando di capire se questo abbia senso.

La documentazione di epsilon() dice:

La funzione restituisce la differenza tra 1 e il valore più piccolo maggiore di 1 che è rappresentabile [da un doppio].

Questo vale anche per 0, cioè epsilon() è il valore più piccolo maggiore di 0? O ci sono numeri tra 0 e 0 + epsilon che possono essere rappresentati da un double ?

Se no, allora il confronto non è equivalente a someValue == 0.0 ?

Supponendo che il doppio IEEE a 64 bit, esiste una mantissa a 52 bit e un esponente a 11 bit. Guarda i seguenti numeri:

 1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1 

Il numero minimo rappresentabile maggiore di 1:

 1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52 

Perciò:

 epsilon = (1 + 2^-52) - 1 = 2^-52 

Ci sono numeri tra 0 e epsilon? Un sacco … Ad esempio il numero minimo rappresentabile positivo (normale) è:

 1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022 

Infatti ci sono circa (1022 - 52 + 1)×2^52 = 4372995238176751616 numeri tra 0 ed epsilon, che è circa il 47% di tutti i numeri rappresentabili positivi …

Il test certamente non è lo stesso di someValue == 0 . L’intera idea dei numeri in virgola mobile è che memorizzano un esponente e un significato. Rappresentano quindi un valore con un certo numero di cifre significative di precisione binarie (53 nel caso di un doppio IEEE). I valori rappresentabili sono molto più densamente imballati vicino a 0 di quanto siano vicini a 1.

Per utilizzare un sistema decimale più familiare, supponiamo di memorizzare un valore decimale “a 4 cifre significative” con esponente. Quindi il successivo valore rappresentabile maggiore di 1 è 1.001 * 10^0 e epsilon è 1.000 * 10^-3 . Ma anche 1.000 * 10^-4 è rappresentabile, supponendo che l’esponente possa memorizzare -4. Puoi credergli che una doppia IEEE può memorizzare esponenti inferiori all’esponente di epsilon .

Non puoi dire da questo codice da solo se ha senso o non usare epsilon specifico come limite, devi guardare il contesto. Può essere che epsilon sia una stima ragionevole dell’errore nel calcolo che ha prodotto someValue , e potrebbe essere che non lo sia.

Esistono numeri che esistono tra 0 ed epsilon perché epsilon è la differenza tra 1 e il successivo numero più alto che può essere rappresentato sopra 1 e non la differenza tra 0 e il successivo numero più alto che può essere rappresentato sopra 0 (se lo fosse, che il codice farebbe molto poco): –

 #include  int main () { struct Doubles { double one; double epsilon; double half_epsilon; } values; values.one = 1.0; values.epsilon = std::numeric_limits::epsilon(); values.half_epsilon = values.epsilon / 2.0; } 

Usando un debugger, ferma il programma alla fine di main e guarda i risultati e vedrai che epsilon / 2 è diverso da epsilon, zero e uno.

Quindi questa funzione prende valori tra +/- epsilon e li rende zero.

Un’approssimazione di epsilon (la minima differenza ansible) attorno a un numero (1.0, 0.0, …) può essere stampata con il seguente programma. Stampa il seguente risultato:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
Un po ‘di riflessione chiarisce che l’epsilon diventa più piccolo, più il numero è minore per il suo valore epsilon, perché l’esponente può adattarsi alle dimensioni di quel numero.

 #include  #include  double getEps (double m) { double approx=1.0; double lastApprox=0.0; while (m+approx!=m) { lastApprox=approx; approx/=2.0; } assert (lastApprox!=0); return lastApprox; } int main () { printf ("epsilon for 0.0 is %e\n", getEps (0.0)); printf ("epsilon for 1.0 is %e\n", getEps (1.0)); return 0; } 

Supponiamo di lavorare con numeri in virgola mobile che si adattano a un registro a 16 bit. C’è un bit di segno, un esponente a 5 bit e una mantissa a 10 bit.

Il valore di questo numero in virgola mobile è la mantissa, interpretata come un valore decimale binario, due volte superiore alla potenza dell’esponente.

Intorno all’1 l’esponente è uguale a zero. Quindi la cifra più piccola della mantissa è una parte nel 1024.

Vicino a 1/2 l’esponente è meno uno, quindi la parte più piccola della mantissa è grande metà. Con un esponente di cinque bit può raggiungere il negativo 16, a quel punto la parte più piccola della mantissa vale una parte in 32m. E con l’esponente 16 negativo, il valore è di circa una parte in 32k, molto più vicino allo zero rispetto all’ epsilon attorno a quello che abbiamo calcolato sopra!

Ora questo è un modello in virgola mobile che non riflette tutte le peculiarità di un vero sistema a virgola mobile, ma la capacità di riflettere valori inferiori a epsilon è ragionevolmente simile ai valori reali a virgola mobile.

Penso che dipenda dalla precisione del tuo computer. Dai un’occhiata a questo tavolo : puoi vedere che se il tuo epsilon è rappresentato da doppio, ma la tua precisione è più alta, il confronto non è equivalente a

 someValue == 0.0 

Buona domanda comunque!

Non è ansible applicarlo a 0, a causa delle parti mantissa ed esponente. Grazie all’esponente puoi memorizzare pochissimi numeri, che sono più piccoli di epsilon, ma quando provi a fare qualcosa del tipo (1.0 – “numero molto piccolo”) otterrai 1.0. Epsilon è un indicatore non di valore, ma di precisione del valore, che è in mantissa. Mostra quanti numeri decimali consecutivi corretti di numeri possiamo memorizzare.

La differenza tra X e il valore successivo di X varia in base a X
epsilon() è solo la differenza tra 1 e il successivo valore di 1 .
La differenza tra 0 e il successivo valore di 0 non è epsilon() .

Invece puoi usare std::nextafter per confrontare un doppio valore con 0 come segue:

 bool same(double a, double b) { return std::nextafter(a, std::numeric_limits::lowest()) <= b && std::nextafter(a, std::numeric_limits::max()) >= b; } double someValue = ... if (same (someValue, 0.0)) { someValue = 0.0; } 

Quindi diciamo che il sistema non può distinguere 1.000000000000000000000 e 1.000000000000000000001. questo è 1.0 e 1.0 + 1e-20. Pensi che ci siano ancora alcuni valori che possono essere rappresentati tra -1e-20 e + 1e-20?

Con il punto di virgola mobile IEEE, tra il più piccolo valore positivo diverso da zero e il più piccolo valore negativo diverso da zero, esistono due valori: zero positivo e zero negativo. Verificare se un valore è compreso tra i valori minori non zero equivale a test per l’uguaglianza con zero; l’assegnazione, tuttavia, potrebbe avere un effetto, poiché cambierebbe uno zero negativo in uno zero positivo.

Sarebbe concepibile che un formato in virgola mobile possa avere tre valori tra i più piccoli valori positivi e negativi finiti: infinitesimale positivo, zero senza segno e infinitesimo negativo. Non ho familiarità con i formati a virgola mobile che di fatto funzionano in questo modo, ma un comportamento del genere sarebbe perfettamente ragionevole e probabilmente migliore di quello di IEEE (forse non è ancora meglio aggiungere altro hardware per supportarlo, ma matematicamente 1 / (1 / INF), 1 / (- 1 / INF) e 1 / (1-1) dovrebbero rappresentare tre casi distinti che illustrano tre diversi zeri). Non so se uno standard C imporrebbe che gli infinitesimi firmati, se esistono, dovrebbero essere uguali a zero. In caso contrario, il codice come sopra potrebbe utilmente garantire che, ad esempio, la divisione di un numero ripetutamente per due alla fine si tradurrebbe in zero anziché essere bloccati su “infinitesimale”.

Inoltre, una buona ragione per avere una tale funzione è rimuovere “denormals” (quei numeri molto piccoli che non possono più usare il “1” implicito in testa e avere una rappresentazione FP speciale). Perché vorresti farlo? Perché alcune macchine (in particolare alcuni vecchi Pentium 4) diventano veramente lente durante l’elaborazione dei denormali. Gli altri diventano solo un po ‘più lenti. Se la tua applicazione non ha davvero bisogno di questi numeri molto piccoli, scovarli a zero è una buona soluzione. Buoni posti per considerare questo sono gli ultimi passaggi di eventuali filtri IIR o funzioni di decadimento.

Vedi anche: Perché la modifica da 0.1f a 0 rallenta le prestazioni di 10 volte?

e http://en.wikipedia.org/wiki/Denormal_number