2009-02-26 22 views
9

Mi chiedevo se esiste un modo per superare un problema di accuratezza che sembra essere il risultato di rappresentazione interna della mia macchina dei numeri in virgola mobile:Trattare con problemi di precisione in numeri in virgola mobile

Per motivi di chiarezza il problema è riassunta come:

// str is "4.600"; atof(str) is 4.5999999999999996 
double mw = atof(str) 

// The variables used in the columns calculation below are: 
// 
//     mw = 4.5999999999999996 
//     p = 0.2 
//     g = 0.2 
//     h = 1 (integer) 

int columns = (int) ((mw - (h * 11 * p))/((h * 11 * p) + g)) + 1; 

Prima di colata per un numero intero digitare il risultato di colonne calcolo è 1,9999999999999996; così vicino ma così lontano dal risultato desiderato di 2.0.

Qualsiasi suggerimento benvenuto.

+0

questa domanda è stato chiesto e ha risposto prima ... solo in cerca di esso ... –

+0

Leggi sull'analisi numerica, è un grosso problema in certe situazioni. Forse usare librerie matematiche alternative (ma più lente) come BigDecimal, ecc ... – JeeBee

risposta

4

Un modo molto semplice ed efficace per sommato un numero in virgola mobile in un intero:

int rounded = (int)(f + 0.5); 

Nota: questo funziona solo se f è sempre positivo. (Grazie a un hacker casuale)

+0

Supponendo che f sia positivo. –

+0

Sì "colonne" è sempre positivo in questa applicazione. – AndyUK

+0

@j_random_hacker: in teoria potresti usare il valore assoluto. – Moshe

1

Utilizzare decimali: decNumber++

+2

Questo risolve il problema 3 * (1/3)? O solo il problema 10 * (1/10)? – MSalters

+1

-1, esattamente per la ragione che ha dato MSalters. I numeri decimali sono utili per lavorare con i soldi non perché hanno una precisione superiore, ma perché i tuoi calcoli imprecisi saranno identici a tutti gli altri ". In tutti gli altri aspetti i numeri decimali soffrono degli stessi identici problemi. –

+0

Sebbene ci siano alcune librerie che memorizzano le frazioni. 4.6 sarebbe 4 + 3/5 in uno di questi. Crollano solo quando viene data un'operazione impossibile da gestire come una frazione, come moltiplicando per pi. –

2

si può leggere questo paper per trovare quello che stai cercando.

È possibile ottenere il valore assoluto del risultato come visto here:

x = 0.2; 
y = 0.3; 
equal = (Math.abs(x - y) < 0.000001) 
11

Se non avete letto il titolo di this paper è propriamente corretto. Si prega di prendere in considerazione la lettura, per saperne di più sui fondamenti dell'aritmetica in virgola mobile sui computer moderni, alcune insidie ​​e spiegazioni sul perché si comportano come loro.

11

Nessun problema di accuratezza.

Il risultato ottenuto (1,9999999999999996) differiva dal risultato matematico (2) di un margine di 1E-16. Questo è abbastanza accurato, considerando il tuo input "4.600".

Si ha un problema di arrotondamento, ovviamente. L'arrotondamento predefinito in C++ è il troncamento; vuoi qualcosa di simile alla soluzione di Kip. I dettagli dipendono dal tuo dominio esatto, ti aspetti round(-x)== - round(x)?

+0

+1 per aver notato il vero problema. –

5

Se la precisione è davvero importante, si dovrebbe prendere in considerazione l'uso di numeri in virgola mobile a doppia precisione piuttosto che semplicemente in virgola mobile. Anche se dalla tua domanda sembra che tu lo sia già. Tuttavia, hai ancora un problema con il controllo di valori specifici. È necessario il codice lungo le linee di (supponendo che si sta controllando il vostro valore contro zero):

if (abs(value) < epsilon) 
{ 
    // Do Stuff 
} 

dove "Epsilon" è un po 'piccola, ma non valore zero.

+1

Penso che tu intenda "abs (computed_value - expected_value)

+1

Infatti - ma ho detto che il codice era un esempio per il controllo rispetto a zero;) – ChrisF

3

Su computer, i numeri in virgola mobile non sono mai esatti.Sono sempre solo una stretta approssimazione. (1e-16 è chiuso.)

A volte ci sono bit nascosti che non si vedono. A volte le regole base dell'algebra non si applicano più: a * b! = B * a. A volte, confrontare un registro con la memoria mostra queste sottili differenze. O usando un coprocessore matematico vs una libreria a virgola mobile di runtime. (Ho fatto questo waayyy tooo lunga.)

C99 definisce: (sguardo nei math.h)

double round(double x); 
float roundf(float x); 
long double roundl(long double x); 

.

Oppure si può rotolare il proprio:

template<class TYPE> inline int ROUND(const TYPE & x) 
{ return int((x > 0) ? (x + 0.5) : (x - 0.5)); } 

per punti di equivalenza galleggiante, provare:

template<class TYPE> inline TYPE ABS(const TYPE & t) 
{ return t>=0 ? t : - t; } 

template<class TYPE> inline bool FLOAT_EQUIVALENT(
    const TYPE & x, const TYPE & y, const TYPE & epsilon) 
{ return ABS(x-y) < epsilon; } 
Problemi correlati