2010-04-28 10 views
14

Per rendere breve il problema, diciamo che voglio calcolare l'espressione a/(b - c) su float s."epsilon" garantisce davvero qualsiasi cosa nei calcoli in virgola mobile?

per assicurarsi che il risultato è significativo, posso verificare se b e c sono in egual:

float EPS = std::numeric_limits<float>::epsilon(); 
if ((b - c) > EPS || (c - b) > EPS) 
{ 
    return a/(b - c); 
} 

ma i miei test mostrano che non è sufficiente a garantire sia risultati significativi né non mancando di fornire un risultato se è possibile.

Caso 1:

a = 1.0f; 
b = 0.00000003f; 
c = 0.00000002f; 

Risultato: La condizione if non è soddisfatto, ma l'espressione produrrebbe un risultato corretto 100.000.008 (come per la precisione dei galleggianti).

Caso 2:

a = 1e33f; 
b = 0.000003; 
c = 0.000002; 

Risultato: La condizione if è soddisfatta, ma l'espressione non produce un risultato significativo +1.#INF00.

ho trovato sia molto più affidabile per controllare il risultato, non gli argomenti:

const float INF = numeric_limits<float>::infinity(); 
float x = a/(b - c); 
if (-INF < x && x < INF) 
{ 
    return x; 
} 

ma per fare cosa è l'Epsilon allora e perché tutti dicendo Epsilon è buono da usare?

risposta

18

"è necessario utilizzare un epsilon quando si ha a che fare con i float" è una reazione istintiva dei programmatori con una comprensione superficiale dei calcoli a virgola mobile, per confronti in generale (non solo a zero).

Questo di solito è inutile perché non vi dice come ridurre al minimo la propagazione di errori di arrotondamento, ma non dice come evitare problemi di cancellazione o di assorbimento, e anche quando il problema è infatti legato al confronto dei due galleggianti, non ti dice quale valore di epsilon è giusto per quello che stai facendo.

Se non hai letto What Every Computer Scientist Should Know About Floating-Point Arithmetic, è un buon punto di partenza. Oltre a ciò, se sei interessato alla precisione del risultato della divisione nel tuo esempio, devi stimare quanto è stato impreciso b-c da precedente errori di arrotondamento, perché infatti se b-c è piccolo, un piccolo errore assoluto corrisponde a un grande errore assoluto sul risultato. Se la tua preoccupazione è solo che la divisione non debba traboccare, allora il tuo test (sul risultato) è corretto.Non c'è motivo di testare un divisore nullo con numeri in virgola mobile, basta testare l'overflow del risultato, che cattura entrambi i casi in cui il divisore è nullo e dove il divisore è così piccolo da rendere il risultato non rappresentabile con qualsiasi precisione.

Per quanto riguarda la propagazione degli errori di arrotondamento, esiste specialized analyzers che può aiutare a stimarlo, perché è una cosa noiosa da fare a mano.

+5

"non ti dice quale valore di epsilon è giusto per quello che stai facendo": per favore sottolinea questa frase di più, è davvero importante. –

2

Epsilon viene utilizzato per determinare se due numeri soggetti all'errore di arrotondamento sono abbastanza vicini da essere considerati "uguali". Si noti che è meglio testare fabs(b/c - 1) < EPS rispetto a fabs(b-c) < EPS e anche meglio — grazie alla progettazione dei galleggianti IEEE — per provare abs(*(int*)&b - *(int*)&c) < EPSI (dove EPSI è un numero intero piccolo).

Il problema è di natura diversa e probabilmente richiede di testare il risultato anziché gli input.

+2

Il pubblico di "regole di aliasing rigoroso" avrebbe qualcosa da dire sul proprio costrutto '* (int *) &'. Personalmente non sono d'accordo con l'atteggiamento dei compilatori che enfatizzano i tempi di riferimento rispetto alle pratiche esistenti e la comprensione diffusa da parte dei programmatori del significato della lingua, ma hanno un punto: il vostro codice sarà mal interpretato dai moderni compilatori. –

+0

Grazie per il commento, Pascal. Potrei avvolgere tutto in un sindacato, ma sarebbe un po 'troppo verboso per un punto così tangenziale. –

+0

Attento con il trucco '* (int *) &': a parte il problema di punteggiatura di tipo che Pascal menziona, non funzionerà anche vicino a 0: se b e c sono molto vicini ma hanno segni opposti (es. B = 2e-45f , c = -0.0f) quindi il test fallirà. –

Problemi correlati