2010-03-28 19 views
17

Il problema.C++ perdita di precisione in virgola mobile: 3015/0.00025298219406977296

Compilatore Microsoft Visual C++ 2005, 32 bit windows xp sp3, amd 64 x2 cpu.

Codice:

double a = 3015.0; 
double b = 0.00025298219406977296; 
//*((unsigned __int64*)(&a)) == 0x40a78e0000000000 
//*((unsigned __int64*)(&b)) == 0x3f30945640000000 
double f = a/b;//3015/0.00025298219406977296; 

il risultato del calcolo (cioè "f") è 11917835,000 milioni (((unsigned __int64) (& f)) == 0x4166bb4160000000) anche se dovrebbe essere 11.917.834,814,763514 millions (cioè ((non firmato __int64) (& f)) == 0x4166bb415a128aef).
I.e. la parte frazionaria è persa.
Sfortunatamente, ho bisogno che la parte frazionaria sia corretta.

Domande:
1) Perché succede?
2) Come posso risolvere il problema?

Ulteriori informazioni:
0) Il risultato è preso direttamente dalla finestra "guardare" (che non è stato stampato, e non mi ha dimenticato di impostare la stampa di precisione). Ho anche fornito un dump esadecimale di variabile in virgola mobile, quindi sono assolutamente sicuro del risultato del calcolo.
1) Lo smontaggio f = a/b è:

fld   qword ptr [a] 
fdiv  qword ptr [b] 
fstp  qword ptr [f] 

2) f = 3015/,00025298219406977296; rendimenti risultato corretto (f == 11.917.834,814,763514 millions, ((unsigned __int64) (& f)) == 0x4166bb415a128aef), ma sembra che in questo caso è semplicemente risultato calcolato durante fase di compilazione:

fld   qword ptr [[email protected] (828EA0h)] 
fstp  qword ptr [f] 

Così , come posso risolvere questo problema?

P.S. Ho trovato una soluzione temporanea (ho bisogno solo di una parte frazionaria della divisione, quindi uso semplicemente f = fmod (a/b)/b al momento), ma mi piacerebbe comunque sapere come risolvere correttamente questo problema - double la precisione dovrebbe essere di 16 cifre decimali, quindi questo calcolo non dovrebbe causare problemi.

risposta

15

Stai usando DirectX nel programma ovunque come che causa l'unità in virgola mobile per ottenere passati a modalità singola precisione di meno che in particolare non dice di quando si crea il dispositivo e potrebbe causare esattamente questo

+1

Questa è una risposta corretta. Il programma utilizza Direct3D e, ovviamente, il calcolo avviene dopo la creazione del dispositivo. La cosa divertente è che sapevo di D3D che regola la precisione della FPU, ma me ne sono completamente dimenticato, perché non ho visto questo errore negli ultimi anni. Problema risolto. – SigTerm

+1

Quale contrassegno deve essere utilizzato durante la creazione del dispositivo? Lo stesso problema esiste con Direct2D? – dalle

1

Suppongo che stiate stampando il numero senza specificare una precisione. Prova questo:

#include <iostream> 
#include <iomanip> 

int main() { 
    double a = 3015.0; 
    double b = 0.00025298219406977296; 
    double f = a/b; 

    std::cout << std::fixed << std::setprecision(15) << f << std::endl; 
    return 0; 
} 

Questo produce:

11917834,814763514000000

che sembra corretto a me. Sto usando VC++ 2008 invece del 2005, ma suppongo che la differenza sia nel codice, non nel compilatore.

+0

No, non sto stampando il numero, il risultato è preso direttamente dalla finestra "guarda". – SigTerm

+0

Hai provato a stamparlo? Forse il bug è nella finestra di controllo !! –

+0

@ Martin La finestra di controllo mostra la precisione completa. –

4

È interessante notare che se dichiarate sia a che b come float, otterrete esattamente 11917835.000000000. Quindi la mia ipotesi è che ci sia una conversione alla precisione singola che accade da qualche parte, sia nel modo in cui le costanti vengono interpretate o più avanti nei calcoli.

Ogni caso è un po 'sorprendente, tuttavia, considerando quanto sia semplice il tuo codice. Non stai usando nessuna direttiva del compilatore esotica, forzando una precisione singola per tutti i numeri in virgola mobile?

Modifica: hai effettivamente confermato che il programma compilato genera un risultato errato? In caso contrario, il candidato più probabile per la conversione di precisione singola (errata) sarebbe il debugger.

+0

Non c'è cast per la precisione singola, come mostra chiaramente lo smontaggio. –

+0

Non su quelle tre linee, comunque. –

2

Se è necessaria una matematica precisa, non utilizzare il virgola mobile.

Fatevi un favore e ottenete una libreria BigNum con supporto numerico razionale.

+3

Non ha bisogno di 11917834.814763514100059144562708, ha solo bisogno di 11917834.814763514. Rinunciare a ordini di grandezza di prestazioni e memoria solo per ottenere la precisione incorporata nella macchina sembra un po 'irrazionale (scusate il gioco di parole). – Gabe

+0

Certo, non abbiamo il diritto di aspettarci l'esattezza, ma abbiamo ancora il diritto di chiedere quel livello di correttezza che la specifica in virgola mobile ci promette! – AakashM

+0

Senza offesa, ma penso che l'utilizzo di bignum solo per un calcolo sia un po 'eccessivo, almeno in questo caso. – SigTerm

0

Sei sicuro di aver esaminato il valore di f subito dopo l'istruzione fstp? Se hai attivato le ottimizzazioni, forse la finestra dell'orologio potrebbe mostrare un valore preso in un secondo momento (questo sembra un po 'plausibile mentre dici che stai guardando la parte frazionaria di f dopo - alcune istruzioni finiscono per mascherarlo in qualche modo?)

Problemi correlati