Genauigkeitsproblem
-
Hallo,
ich habe leider ein kleines Genauigkeitsproblem:Ich habe verschiedene Gebäude, die pro Frame einmal angesprochen werden, um die Abrechnungen zu machen. Als Parameter wird dieser Funktion dann die verstrichende Zeit seit dem letzten Frame mitgegeben:
BOOL CArbeiterGebeude::Move(float fTime) { long double fTime2 = (long double) fTime; char acText2[512]; static std::ofstream schreiben("C:\\DEBUG2.txt"); if(g_pSpiel->m_pGame->m_babrechnen) { sprintf_s(acText2, "\nZeit1: %.5f\nZeit2: %.5f\nVORHER:\n\nEinnahmen: %.5f \nAusgaben: %.5f \nBilanz1: %.5f \nGeld: %.5f \nGeld_old %.5f \nBilanz2: %.5f ", fTime, fTime2, g_pSpiel->m_pGame->m_dEinnahmen, g_pSpiel->m_pGame->m_dAusgaben, g_pSpiel->m_pGame->m_dEinnahmen-g_pSpiel->m_pGame->m_dAusgaben, g_pSpiel->m_pGame->m_dGeld, g_pSpiel->m_pGame->m_dGeldold, g_pSpiel->m_pGame->m_dGeld-g_pSpiel->m_pGame->m_dGeldold); schreiben<<acText2; } long double WertT =m_sTyp->Kosten_Sanierung*fTime2/10.0; g_pSpiel->m_pGame->m_dGeld -= WertT; g_pSpiel->m_pGame->m_dAusgaben += WertT; if(g_pSpiel->m_pGame->m_babrechnen) { sprintf_s(acText2, "\n\nNACHHER:\n\nEinnahmen: %.5f \nAusgaben: %.5f \nBilanz1: %.5f \nGeld: %.5f \nGeld_old %.5f \nBilanz2: %.5f ", g_pSpiel->m_pGame->m_dEinnahmen, g_pSpiel->m_pGame->m_dAusgaben, g_pSpiel->m_pGame->m_dEinnahmen-g_pSpiel->m_pGame->m_dAusgaben,g_pSpiel->m_pGame->m_dGeld, g_pSpiel->m_pGame->m_dGeldold, g_pSpiel->m_pGame->m_dGeld-g_pSpiel->m_pGame->m_dGeldold); schreiben<<acText2; }
Wenn am Ende dann die Differenz zwischen m_dAusgaben und m_dEinnahmen für die Bilanz berechnet wird, gibt es leider eine Spanne zwischen der wirkliche Differenz zwischen dem ursprünglichen Geldbetrag und dem jetzigen Geldbetrag.
Wenn die Differenz nicht größer als eins oder zwei wäre, dann fänd ich das nicht so schlimm. Aber diese Abweichung hat pro 10 Sekunden schon Werte um die 30 erreicht.
Deshalb hab ich vor und nach der Rechnung diese beiden Blöcke eingefügt, die die aktuellen Daten ausgeben sollen, damit man einen besseren Überblick über die Abrechnung bekommt:Zeit1: 0.05801
Zeit2: 0.05801
VORHER:Einnahmen: 0.00000
Ausgaben: 0.00000
Bilanz1: 0.00000
Geld: 504634.18750
Geld_old 504634.18750
Bilanz2: 0.00000NACHHER:
Einnahmen: 0.00000
Ausgaben: 0.17403
Bilanz1: -0.17403
Geld: 504634.00000
Geld_old 504634.18750
Bilanz2: -0.18750Dies ist die Ausgabe einer Abbrechnung, bei der Kosten_Sanierung 30 betrug.
Leider kann man hier an dieser einen Abrechnung schon erkennen, dass bereits nach nur einer Abbrechnung die Bilanzen nicht mehr zueinander passen.
Kann mir einer vielleicht erklären, woran das liegen könnte, ich hab extra schon "long double" als Datentyp genommen, um eben große Genauigkeit zu haben.In einem Vergleichsprojekt, in dem die ausgegebenen Daten direkt eingetragen worden sind tritt der Fehler aber leider nicht auf:
long double m_dGeld = 504634.18750; long double m_dGeldold = 504634.18750; long double m_dAusgaben = 0.0; long double m_dEinnahmen = 0.0; printf("\nVORHER:\n\nEinnahmen: %.5f \nAusgaben: %.5f \nBilanz1: %.5f \nGeld: %.5f \nGeld_old %.5f \nBilanz2: %.5f ", m_dEinnahmen, m_dAusgaben, m_dEinnahmen-m_dAusgaben, m_dGeld, m_dGeldold, m_dGeld-m_dGeldold); int Kosten_Sanierung=30; float fTime = 0.05801f; long double WertT =Kosten_Sanierung*fTime/10.0; m_dGeld -= WertT; m_dAusgaben += WertT; printf("\n\nNACHHER:\n\nEinnahmen: %.5f \nAusgaben: %.5f \nBilanz1: %.5f \nGeld: %.5f \nGeld_old %.5f \nBilanz2: %.5f ", m_dEinnahmen, m_dAusgaben, m_dEinnahmen-m_dAusgaben,m_dGeld, m_dGeldold, m_dGeld-m_dGeldold);
Bei diesem Code passend die Bilanzen zueinander.
Hat jemand eine Idee, worin das Problem liegt, und wie ich es beheben kann?
Muss man einen noch genaueren Datentyp als "long double" nehmen? Aber warum tritt das Problem dann nicht bei dem Projekt auf, indem die Daten direkt so hereingeschrieben worden sind?
(ich verwende VC 2005)Vielen Dank für Hilfe, viele Grüße
Andreas
-
Hi, welchen Datentyp haben denn die Member der Struktur 'g_pSpiel->m_pGame' (ok, sehe gerade, daß du sie ja mit "%.5f" ausgibst, also sind es nur float-Werte)?
Am besten du nimmst als Standarddatentypen 'double' (long double benötigst du nur bei sehr großen Werten bzw. sehr großer Genauigkeit).
Es bringt bei dir ja nichts, wenn du in deiner Funktion lokal zwar mit long double rechnest, aber nur float-Werte dann abspeicherst (und beim nächsten Durchlauf dann damit weiterrechnest).Müßtest du nicht eigentlich mit "%.5Lf" long double Werte ausgeben???
(alternativ kannst du den Streaming Operator << dafür in C++ benutzen!)P.S. Noch als Tipp: ich würde dir raten einen lokalen Zeiger (bzw. besser noch eine Referenz) auf 'g_pSpiel->m_pGame' anzulegen, damit man das besser lesen kann!
-
Hi, erstmal vielen Dank für die Antwort.
Tut mir leid das ich das nicht dabei geschrieben habe, aber wenn du bei den Membervariablen von m_pGame m_dGeld und so meinst dann sind das auch long double Werte. (Das untere Beispiel sollte eine Originalgetreue Nachbildung von dem Code sein, bei dem der Fehler auftritt, nur wurden hier die Anfangszahlen manuell nach den ausgegebenen Werten festgelegt)
Bei der Ausgabe hab ich einfach .5f genommen, da es bisher auch bei double-Werten funktioniert hatte.
Ich glaub aber nicht, dass die fehlerhaften Zahlen an der Ausgabe liegen, ich hab mir im Debugger jeden Schritt anzeigen lassen und die Variablen überwacht, wobei hierbei auch der gleiche Fehler aufgetreten ist.Kann es sonst noch einen Grund geben der zu dieser Ungenauigkeit führt?
Viele Grüße
Andreas
-
Das Problem ist, dass double (auch long double) immer fehlerbehaftet ist, sobald es nicht mehr um zweierpotenzen geht. Stell dir ein System mit Fließkommazahlen im Zehnersystem vor, in dem wild mit siebteln, dritteln und anderen eigentlich periodischen Zahlen herumgerechnet wird. So ein System hat eine entdliche Genauigkeit, schneidet also irgendwo die Mantissen ab und bekommt Rundungsfehler. Das Selbe passiert bei double & Co, nur dass hier im zweiersystem gerechnet wird. Gerade wenn du schon so große Zahlen vor dem Komma hast, nimmt die Genauigkeit hinter dem Komma ab.
Da es sich bei Geld immer um eine Größe mit fester Nachkommazahl handelt, ist es am sinnvollsten, intern mit Integerzahlen zu rechnen und bei der Ausgabe dann mit einem entsprechend eingestreuten Komma zu versehen.
-
Also ich tippe immer noch darauf, daß irgendwo nur mit floats gerechnet (bzw. abgespeichert) wird, da die anderen Datentypen eine wesentlich größere Präzision haben:
float: ca. 6-7
double: ca. 15
long double: ca. 34Aber für Geldwerte solltest du (so wie pumuckl schon geschrieben hat), am besten mit Integern (int, long, long long) arbeiten, um mögliche Rundungsfehler zu vermeiden.
P.S: Wo (bzw. wann) setzt du eigentlich 'g_pSpiel->m_pGame->m_dGeldold'?
-
Hallo, erstmal vielen Dank für die Antworten!!!
Die Variablen wurden so deklariert in Game.h:
long double m_dGeld; long double m_dAusgaben; // Ausgaben für einen Monat long double m_dEinnahmen; // Einnahmen für einen Monat long double m_daAusgaben; // Ausgaben für einen Monat long double m_daEinnahmen; // Einnahmen für einen Monat long double m_dGeldold; double m_dBilanz2;
m_dGeldold wird folgendermaßen gesetzt:
if(m_fTime > DauerMonat) { //schreiben<<"NEUNER MONAT\n\n"; machAbrechnung(fTime-(m_fTime-DauerMonat)); //machAbrechnung(10.0f); m_iMonat++; m_bNeuer_Monat = TRUE; m_iHauslvlUP = 0; m_dBilanz = m_dEinnahmen-m_dAusgaben; m_dBilanz2 = m_dGeld-m_dGeldold; m_dGeldold = m_dGeld; m_daEinnahmen = m_dEinnahmen; m_dEinnahmen = 0.0; m_daAusgaben = m_dAusgaben; m_dAusgaben = 0.0; .... }
Die gezeigte Abrechnung von den vorherigen Zahlen kommt direkt nach diesem Code, da ja m_dAusgaben und Co noch auf Null standen.
Ich hab den Code nochmal debugged und mir die Zahlen genau angeschaut:
long double WertT =m_sTyp->Kosten_Sanierung*fTime2/10.0; // 1. Zeile g_pSpiel->m_pGame->m_dGeld -= WertT; // Zweite Zeile g_pSpiel->m_pGame->m_dAusgaben += WertT; //Dritte Zeile
Ich hab jeweils nach dem abarbeiten einer Zeile mir die Variablen ausgeben lassen:
vor der ersten Zeile: WertT -9.2559631349317831e+061 double fTime2 0.040189463645219803 double fTime 0.040189464 float g_pSpiel->m_pGame->m_dGeld 504610.56250000000 double g_pSpiel->m_pGame->m_dAusgaben 0.00000000000000000 double g_pSpiel->m_pGame->m_dGeldold 504610.56250000000 double m_sTyp->Kosten_Sanierung 30 int Nach der ersten Zeile: WertT 0.12056839466094971 double fTime2 0.040189463645219803 double fTime 0.040189464 float g_pSpiel->m_pGame->m_dGeld 504610.56250000000 double g_pSpiel->m_pGame->m_dAusgaben 0.00000000000000000 double g_pSpiel->m_pGame->m_dGeldold 504610.56250000000 double m_sTyp->Kosten_Sanierung 30 int Nach der zweiten Zeile: WertT 0.12056839466094971 double fTime2 0.040189463645219803 double fTime 0.040189464 float g_pSpiel->m_pGame->m_dGeld 504610.43750000000 double g_pSpiel->m_pGame->m_dAusgaben 0.00000000000000000 double g_pSpiel->m_pGame->m_dGeldold 504610.56250000000 double m_sTyp->Kosten_Sanierung 30 int Nach der dritten Zeile: WertT 0.12056839466094971 double fTime2 0.040189463645219803 double fTime 0.040189464 float g_pSpiel->m_pGame->m_dGeld 504610.43750000000 double g_pSpiel->m_pGame->m_dAusgaben 0.12056839466094971 double g_pSpiel->m_pGame->m_dGeldold 504610.56250000000 double m_sTyp->Kosten_Sanierung 30 int
Mir sind da jetzt ein paar Dinge beim Nachrechnen mit dem Taschenrechner aufgefallen:
-
WertT hat der Computer so berechnet: 0.12056839466094971
nach meinem Taschenrechner: 0.1205683909
=> Macht eine Abweichung ab der 8. Nachkommastelle -
Der Debugger hat hinter die Variablenangabe jeweils den Typ geschrieben, wobei dort aber immer nur double und nie long double stand, kann das sein, dass VC 2005 long double nicht umgesetzt hat, oder ist das nur eine verkürtzte Debuggerausgabe?
-
Nach dem abarbeiten der zweiten Zeile, habe ich mal mit dem Taschenrechner die Differenz zwischen Geld und Geldold berechnet, wobei dabei mehrfach eine Differenz von 0,125 zustande kam, obwohl man eigentlich eine Differenz von 0.1205...(WertT) erwarten würde. Das mit 0,125 würde wieder für das Problem mit den zweier Potenzen sprechen, da 0,125 ja 2^-3 ist.
-
Der Debugger gibt die Variablen auf sehr viele Nachkommastellen aus, bsp: 504610.43750000000, wobei allerdings sehr viele dabei Null bleiben, welches eigentlich nicht sein dürfte.
In dem Erstatzprojekt, in dem die Berechnung klappte sind diese vielen Nullen nicht vorhanden.
Ich hab auch mal folgendes noch in das Fehlerprojekt geschrieben:long double dummy6 = 500000.7328123456798187; dummy6 -= 0.11994317919015884;
Wobei er erst alle Nullen mit den Zahlen ausgefüllt hatte, nach der Subtraktion allerdings wieder nur auf die vierte Nachkommastelle genau war.
Ich kann mir dieses komische Verhalten nicht erklären, liegt das daran, dass es unterschiedliche Projekteinstellungen gibt, die den Compiler veranlassen stark zu optimieren? (Das Fehlerprojekt ist eine Win 32-Anwendung mit GUI, das andere eine Konsolen-Anwendung)
Ich bin ehrlich gesagt, ziemlich ratlos, was man dagegen machen kann, wisst ihr vielleicht etwas?
(In beiden Projekten steht zumindest "Gleitkommamodell" auf "Precise (/fp:precise)".
Viele Grüße
Andreas
-
-
Hallo, erstmal vielen Dank für die ganzen Antworten,
ich hab den Fehler jetzt endlich gefundenDirectX hat mir die Genauigkeit des FPU herabgesetzt, ich hätte das Flag D3DCREATE_FPU_PRESERVE mitangeben müssen, damit dieses nicht passiert.
Viele Grüße
Andreas