2012-03-03 21 views
8

Ho un algoritmo che utilizza floats o doubles per eseguire alcuni calcoli.float/doppia uguaglianza con lo zero esatto

Esempio:

double a; 
double b; 
double c; 
... 
double result = c/(b - a); 
if ((result > 0) && (result < small_number)) 
{ 
    // result is relevant... 
} else { 
    // result not required... 
} 

Ora, io sono preoccupato per (b - a) potrebbe essere pari a zero. Se è vicino a zero ma non a zero, non importa perché il result sarà fuori portata per essere utile, e già noto che (dato che (b - a) si avvicina a zero, result si avvicina a +/- inf, che non è nel variare 0-small_number ...)

Ma se il risultato di (b - a) è esattamente pari a zero, mi aspetto che qualcosa dipendente dalla piattaforma accadrà a causa di dividere per zero. Ho potuto modificare l'istruzione if a:

if ((!((b-a) == 0.0)) && ((result = c/(b-a)) > 0) && (result < small_number)) { 

ma non so se (b-a) == 0.0 rileverà sempre uguaglianza con zero. Ho visto che ci sono più rappresentazioni per lo zero esatto in virgola mobile? Come puoi testarli tutti senza fare qualche controllo epsilon, cosa di cui non ho bisogno (un piccolo epsilon verrà ignorato nel mio algoritmo)?

Qual è il modo indipendente dalla piattaforma per controllare?

EDIT:

Non so se era abbastanza chiaro alla gente. Fondamentalmente voglio sapere come trovare se un'espressione come:

double result = numerator/denominator; 

si tradurrà in un'un'eccezione a virgola mobile, un'eccezione cpu, un segnale dal sistema operativo o qualche altra cosa .... senza eseguire effettivamente il funzionamento e vedere se "getterà" ... perché rilevare un "lancio" di questa natura sembra essere complicato e specifico della piattaforma.

È sufficiente il numero ((denominator==0.0) || (denominator==-0.0)) ? "Will 'throw'" : "Won't 'throw'";?

+1

Anche se 'b - a' non è esattamente pari a zero, l'operazione' c/(b - a) 'potrebbe ancora troppo pieno e inviare il valore di' +/- INF'. – Mysticial

+0

@Mystical Va bene, metterebbe il risultato al di fuori dell'intervallo di interesse per il mio problema. – Bingo

+8

È difficile trovare un'implementazione che non utilizza la virgola mobile IEEE. Quindi penso che sia sicuro assumere che la divisione per '0 'darà semplicemente' + INF' o '-INF'? Qualcuno dovrebbe correggermi se sbaglio? – Mysticial

risposta

9

Dipende dal modo in cui b e a hanno ottenuto i loro valori. Zero ha una rappresentazione esatta nel formato in virgola mobile, ma il problema più grande sarebbe quasi-ma-non-proprio zero valori. Sarebbe sempre sicuro controllare:

if (abs(b-a) > 0.00000001 && ... 

Dove 0,00000001 è qualunque valore abbia senso.

+5

Perché esattamente un numero quasi-non-proprio-zero può essere un problema? Il mio algoritmo rigetta già i risultati da un denominatore quasi quasi non-zero ... – Bingo

+0

@Bingo: Non vedo il tuo algoritmo rifiutare 'b - a' con un valore di' 1e-300'. Tale test costa poco ed è una grande assicurazione per resistere alla morphing del codice, anche se una versione di esso non può produrre quel valore. – wallyk

+1

il punto è che qualsiasi cosa divisa per quasi zero è grande ... al di fuori dell'intervallo tra '0' e' small_number', che è l'intervallo che mi interessa. – Bingo

-1

Si potrebbe provare

if ((b-a)!=(a-b) && ((result = c/(b-a)) > 0) && (result < small_number))) { 
... 
+0

Non esattamente sicuro, ma sei sicuro che '(b-a)' e '(a-b)' produrrebbe la stessa versione di zero in virgola mobile? – Bingo

0

UPDATE (2016-01-04)

ho ricevuto alcune downvotes su questa risposta, e mi chiedevo se devo semplicemente eliminarlo. Sembra che il consenso (https://meta.stackexchange.com/questions/146403/should-i-delete-my-answers) sia che l'eliminazione delle risposte dovrebbe essere eseguita solo in casi estremi.

Quindi, la mia risposta è sbagliata. Ma immagino che me ne stia andando perché offre un interessante esperimento di pensiero "fuori dagli schemi".

===============

Bingo,

Tu dici che vuoi sapere se ba == 0.

Un altro modo di guardare questo è per determinare se un == b. Se a è uguale a b, allora BA sarà pari a 0.

Un'altra idea interessante che ho trovato:

http://www.cygnus-software.com/papers/comparingfloats/Comparing%20floating%20point%20numbers.htm

In sostanza, si prende le variabili in virgola mobile che avete e dite al compilatore di reinterpretare loro (un po ' per bit) come interi con segno, come nel seguente:

if (*(int*)&b == *(int*)&a) 

interi Poi si confrontano e punti non galleggianti. Forse questo ti aiuterà? Forse no. In bocca al lupo!

+2

Avrei votato questo se non fosse stato per la cosa piuttosto brutta del casting. 'if (a! = b) {/ * do the math * /}' è abbastanza buono per questo caso; qualsiasi altra cosa è eccessiva. – cHao

+0

Grazie per la risposta. Vale la pena pensare all'idea di uguaglianza. Sarebbe bello per una soluzione più generale quando il denominatore non era il risultato di una sottrazione però. Inoltre, penso che il confronto tra bit a bit (che è l'approccio integer) sia il problema qui. Diverse rappresentazioni di zero avrebbero comunque diversi schemi di bit. – Bingo

+3

Non solo '* (int *) & b == * (int *) & a' rompe le rigide regole di aliasing, ma rende' + 0' come differente da '-0'. Non vedo alcun motivo per usare questo confronto qui. –

0

Credo che (b-a)==0 sarà vero esattamente in quei casi in cui il c/(b-a) non riuscirebbe a causa di (b-a) zero. La matematica fluttuante è complicata, ma a mio avviso questo è esagerato. Inoltre, credo che lo (b-a)==0 sarà equivalente a b!=a.

La distinzione positiva e negativa 0 non è necessaria. Vedi per es. qui Does float have a negative zero? (-0f)

+0

Per quanto riguarda "l'ordine di valutazione delle sottoespressioni nelle tue condizioni non è definito in C++ ", in' e1 && e2', l'ordine di valutazione ** è ** definito. 'e2' viene sempre valutato dopo' e1' e solo se 'e1' viene valutato su vero/diverso da zero. –

+0

Hai ragione! http://stackoverflow.com/questions/7112282/order-of-evaluation-of-operands Cancellerò quella parte. Resto della risposta lascio anche se la parte eliminata è stata la ragione per cui ho deciso di rispondere ... –

+0

La divisione con lo zero virgola mobile è ben definita se il compilatore segue gli standard IEEE. [Ulteriori informazioni su SO] (http://stackoverflow.com/questions/12617408/a-few-things-about-division-by-zero-in-c) o leggi [lo standard IEEE in virgola mobile] (http://en.wikipedia.org/wiki/Division_by_zero#In_computer_arithmetic)! – DanielTuzes

4

Ecco come si fa: invece di controllare per (result < small_number), si controlla la

(abs(c) < abs(b - a) * small_number) 

Quindi tutti i tuoi problemi spariscono! Il calcolo di c/(b-a) non verrà mai superato se questo test viene superato.

0

Per epsilon, in c'è una definizione di modello standard std :: numeric_limits :: epsilon(). Immagino che controllare la differenza sia maggiore di std :: numeric_limits :: epsilon() dovrebbe essere abbastanza sicuro da proteggere dalla divisione per zero. Nessuna dipendenza dalla piattaforma, immagino.

+0

Questa risposta non ha senso. 'std :: numeric_limits :: epsilon()' non è inteso per questo uso, misura l'ULP appena sopra '1.0'. E quando si divide per 'x', la divisione per zero si verifica solo quando' x == 0' (si può verificare un overflow, ma questo è già stato indicato). –

+0

Giusto, non è una risposta al problema originale, solo un'osservazione relativa a epsilon. Avrei dovuto inserirlo come commento, ma molti post precedenti si riferivano a questo. Ma la tua osservazione è corretta. Maggiori informazioni su questo [http://en.cppreference.com/w/cpp/types/numeric_limits/epsilon] – mcjoan

2

Immagino che tu possa usare fpclassify(-0.0) == FP_ZERO. Ma questo è utile solo se vuoi verificare se qualcuno ha inserito qualche tipo di zero nella variabile di tipo float. Come molti hanno già detto se si desidera verificare il risultato del calcolo, è possibile ottenere valori molto vicini allo zero a causa della natura della rappresentazione.

+0

Si prega di commentare un motivo per il downvoting. Prometto che è solo per capire il mio errore. – ony

0

In breve, possiamo sapere che un numero variabile è ZERO esattamente se sappiamo che rappresenta il formato.


In pratica, confrontiamo x con un piccolo numero. E se x è inferiore a questo numero, pensiamo che x sia uguale a ZERO funzionalmente (ma la maggior parte del tempo il nostro piccolo numero è ancora grande di zero). Questo metodo è molto semplice, efficiente e può essere multipiattaforma.


realtà, il float e double sono state presentate da formato speciale, e ampiamente usato uno è IEEE 754 in hardware corrente che divide il numero in segno, esponente e mantissa bit (significante).

Quindi, se vogliamo controllare se un numero float è ZERO esattamente, siamo in grado di controllare se sia esponente e mantissa è pari a zero, vedi here.

In IEEE 754 numeri in virgola mobile binari, valori zero sono rappresentati dal esponente polarizzato e significando entrambi essendo zero. Lo zero negativo ha il bit di segno impostato su uno.

Prendete float per esempio, possiamo scrivere un semplice codice per estrarre esponente e mantissa po 'e poi controllare.

#include <stdio.h> 

typedef union { 
      float f; 
      struct { 
       unsigned int mantissa : 23; 
       unsigned int exponent : 8; 
       unsigned int sign :  1; 
      } parts; 
} float_cast; 


int isZero(float num) { 

    int flag = 0; 
    float_cast data; 
    data.f = num; 

    // Check both exponent and mantissa parts 
    if(data.parts.exponent == 0u && data.parts.mantissa == 0u) { 
     flag = 1; 
    } else { 
     flag = 0; 
    } 

    return(flag); 
} 


int main() { 

    float num1 = 0.f, num2 = -0.f, num3 = 1.2f; 

    printf("\n is zero of %f -> %d", num1, isZero(num1)); 
    printf("\n is zero of %f -> %d", num2, isZero(num2)); 
    printf("\n is zero of %f -> %d", num3, isZero(num3)); 

    return(0); 
} 

I risultati dei test:

# è pari a zero di 0.000000 -> 1
# è pari a zero di -,000000 -> 1
# è pari a zero di 1,200 mila -> 0


Altri esempi:

Controlliamo quando lo float diventa ZERO reale con codice.

void test() { 
    int i =0; 
    float e = 1.f, small = 1.f; 
    for(i = 0; i < 40; i++) { 
     e *= 10.f; 
     small = 1.f/e; 
     printf("\nis %e zero? : %d", small, isZero(small)); 
    } 
    return; 
} 


is 1.0000e-01 zero? : NO 
is 1.0000e-02 zero? : NO 
is 1.0000e-03 zero? : NO 
is 1.0000e-04 zero? : NO 
is 1.0000e-05 zero? : NO 
is 1.0000e-06 zero? : NO 
is 1.0000e-07 zero? : NO 
is 1.0000e-08 zero? : NO 
is 1.0000e-09 zero? : NO 
is 1.0000e-10 zero? : NO 
is 1.0000e-11 zero? : NO 
is 1.0000e-12 zero? : NO 
is 1.0000e-13 zero? : NO 
is 1.0000e-14 zero? : NO 
is 1.0000e-15 zero? : NO 
is 1.0000e-16 zero? : NO 
is 1.0000e-17 zero? : NO 
is 1.0000e-18 zero? : NO 
is 1.0000e-19 zero? : NO 
is 1.0000e-20 zero? : NO 
is 1.0000e-21 zero? : NO 
is 1.0000e-22 zero? : NO 
is 1.0000e-23 zero? : NO 
is 1.0000e-24 zero? : NO 
is 1.0000e-25 zero? : NO 
is 1.0000e-26 zero? : NO 
is 1.0000e-27 zero? : NO 
is 1.0000e-28 zero? : NO 
is 1.0000e-29 zero? : NO 
is 1.0000e-30 zero? : NO 
is 1.0000e-31 zero? : NO 
is 1.0000e-32 zero? : NO 
is 1.0000e-33 zero? : NO 
is 1.0000e-34 zero? : NO 
is 1.0000e-35 zero? : NO 
is 1.0000e-36 zero? : NO 
is 1.0000e-37 zero? : NO 
is 1.0000e-38 zero? : NO 
is 0.0000e+00 zero? : YES <-- 1e-39 
is 0.0000e+00 zero? : YES <-- 1e-40