2010-03-28 11 views
17

Das Problem.C++ Fließkomma-Präzisionsverlust: 3015/0,00025298219406977296

Microsoft Visual C++ 2005-Compiler, 32-Bit-Windows XP sp3, und 64 x2 CPU.

Code:

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

das Ergebnis der Berechnung (dh "f") ist 11.917.835,000000000 (((unsigned __int64) (& f)) == 0x4166bb4160000000), obwohl es 11.917.834,814763514 sein sollte (dh ((ohne Vorzeichen __int64) (& f)) == 0x4166bb415a128aef).
I.e. Bruchteil ist verloren.
Leider brauche ich Bruchteil, um richtig zu sein.

Fragen:
1) Warum passiert das?
2) Wie kann ich das Problem beheben?

Zusätzliche Informationen:
0) Das Ergebnis genommen wird direkt von „watch“ Fenster (es wurde nicht gedruckt, und ich habe nicht vergessen, den Druck Präzision eingestellt). Ich habe auch einen Hex-Dump der Gleitkommavariablen geliefert, also bin ich mir absolut sicher über das Berechnungsergebnis.
1), um die Demontage von f = a/b ist:

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

2) f = 3015/0,00025298219406977296; korrektes Ergebnis Ausbeuten (f == 11.917.834,814763514, ((unsigned __int64) (& f)) == 0x4166bb415a128aef), aber es sieht aus wie in diesem Fall Ergebnis wird einfach während der Kompilierung Zeit berechnet:

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

So , wie kann ich dieses Problem beheben?

P.S. Ich habe einen temporären Workaround gefunden (ich brauche nur einen Bruchteil der Division, also verwende ich im Moment einfach f = fmod (a/b)/b), aber ich würde trotzdem gerne wissen, wie man dieses Problem richtig löst - doppelt Die Genauigkeit soll 16 Dezimalziffern entsprechen, daher sollte diese Berechnung keine Probleme verursachen.

Antwort

15

Sind Sie mit directx in Ihrem Programm überall wie die Gleitkommaeinheit bewirkt, mit einfacher Genauigkeit Modus zu erhalten schaltet, wenn Sie es ausdrücklich sagen, nicht, wenn Sie das Gerät zu erstellen und würde dazu führen, genau diese

+1

Dies ist eine korrekte Antwort. Das Programm verwendet Direct3D, und natürlich erfolgt die Berechnung nach der Geräteerstellung. Das Lustige daran ist, dass ich wusste, dass D3D die FPU-Genauigkeit anpasst, aber ich habe es völlig vergessen, weil ich diesen Fehler in den letzten Jahren nicht gesehen habe. Problem gelöst. – SigTerm

+1

Welches Flag sollte beim Erstellen des Geräts verwendet werden? Gibt es das gleiche Problem mit Direct2D? – dalle

1

Ich schätze, dass Sie die Zahl ausdrucken, ohne eine Genauigkeit anzugeben. Versuchen Sie folgendes:

#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; 
} 

Dies erzeugt:

11917834,814763514000000

Was mir richtig aussieht. Ich benutze VC++ 2008 statt 2005, aber ich denke, der Unterschied liegt in Ihrem Code, nicht im Compiler.

+0

Nein, ich drucke keine Nummer, das Ergebnis wird direkt aus dem "Watch" -Fenster übernommen. – SigTerm

+0

Haben Sie versucht, es zu drucken? Vielleicht ist der Fehler im Überwachungsfenster !! –

+0

@Martin Das Uhrenfenster zeigt die volle Präzision. –

4

Interessanterweise, wenn Sie sowohl a als auch b als floats deklarieren, erhalten Sie genau 11917835.000000000. Meine Vermutung ist, dass irgendwo eine Umwandlung in eine einfache Genauigkeit stattfindet, entweder in der Interpretation der Konstanten oder später in den Berechnungen.

Jeder Fall ist ein wenig überraschend, wenn man bedenkt, wie einfach Ihr Code ist. Sie verwenden keine exotischen Compiler-Direktiven und erzwingen eine einfache Genauigkeit für alle Gleitkommazahlen?

Bearbeiten: Haben Sie tatsächlich bestätigt, dass das kompilierte Programm ein falsches Ergebnis generiert? Andernfalls wäre der Debugger der wahrscheinlichste Kandidat für die (fehlerhafte) Konvertierung mit einfacher Genauigkeit.

+0

Es gibt keinen Guss auf einfache Genauigkeit, wie die Demontage deutlich zeigt. –

+0

Nicht auf diesen drei Linien, jedenfalls. –

2

Wenn Sie genaue Mathematik benötigen, verwenden Sie keine Gleitkommazahl.

Tun Sie sich einen Gefallen und erhalten Sie eine BigNum-Bibliothek mit Rational-Nummer-Unterstützung.

+3

Er braucht nicht 11917834.814763514100059144562708, er braucht nur 11917834.814763514. Es ist ein bisschen irrational, die Größe der Leistung und des Gedächtnisses aufzugeben, nur um die Präzision in die Maschine zu integrieren (verzeihen Sie das Wortspiel). – Gabe

+0

Wir sind zwar nicht berechtigt, Genauigkeit zu erwarten, aber wir sind berechtigt, nach der Korrektheit zu fragen, die die Fließkommaspezifikation uns verspricht! – AakashM

+0

Nichts für ungut, aber ich denke, mit Bignums nur für eine Berechnung ist ein bisschen zu viel, zumindest in diesem Fall. – SigTerm

0

Sind Sie sicher, dass Sie den Wert von f direkt nach der fstp-Anweisung untersuchen? Wenn Sie Optimierungen eingeschaltet haben, könnte das Watch-Fenster einen Wert zeigen, der zu einem späteren Zeitpunkt aufgenommen wurde (dies erscheint ein wenig plausibel, wenn Sie sagen, dass Sie den Bruchteil von f später betrachten) irgendwie aus?)