Subtraktion mit Double (falsches Resultat)
-
Hallo Zusammen.
Ich habe folgendes Problem.
Ich habe 3 Variabeln vom Typ "double";Die Variabeln werden mit folgenden Werten gefüllt;
double dDateVal1; double dDateVal2; double dCurrentVal; dDateVal1=39923.861817; dDateVal2=39923.86174; //Hier kommt ==> 6.6678246842E-05 //statt ==> 0.000077 dCurrentVal = dDateVal1 - dDateVal2;Was mache ich hier falsch?
Eigentlich sollte diese Anzahl an Kommastellen ja kein Problem darstellen oder sehe ich das falsch.
-
Google mal nach "Fließkomma Genauigkeit" und informier dich über die binäre Darstellung von Fließkommazahlen, dann verstehst du warum

1/3 kann man im 10er System mit endlicher Genauigkeit darstellen: 0.333333
3* (1/3) ist eigentlich 1, aber im 10er-System mit 6 Stellen Genauigkeit ist das 3 * 0.333333 = 0.999999 und nicht 1
-
Wenn eine so hohe Genauigkeit wichtig für deine Anwendung ist wäre eine Möglichkeit z.B. erstmal mit __int64 (oder größer falls deine Platform das kann) zu rechnen und erst am Ende in ein double zurückzurechnen.
In deinem Beispiel sowas:
__int64 iDateVal1; __int64 iDateVal2; __int64 iCurrentVal; iDateVal1 = 39923861817; // 39923.861817 * 1.000.000 iDateVal2 = 39923861740; // 39923.86174 * 1.000.000 iCurrentVal = iDateVal1 - iDateVal2; doulbe dCurrentVal = iCurrentVal / 1000000.0; //spätestens hier ist Schluss mit der hohen Genauigkeit.Sowas bringt aber sehr viele Probleme:
- du musst darauf achten die Grenzen deines Typs(z.B. __int64) nicht zu überschreiten
- wenn du mit einer Zahl rechnen willst die mehr Nachkommestellen als deine bisherigen hat musst du alle erstmal anpassen
- spätestens beim Zurückrechnen in einen double-Wert geht ein Teil der Genauigkeit wieder flöten
- Divisionen liefern dir maximal deine voreingestellte Genauigkeit (im oberen Beispiel 6 Stellen hinterm Koma)
- etc.Daher sollte das nur im sehr kleinen Rahmen und wenn du ganz genau weißt was du tust verwendet werden.
Eine bessere Alternative wäre Punkt 4 der FAQ
-
Hallo
Ich werde mir dies mal anschauen.
Ich habe das ganze nun nochmals durchgeschaut, und ich benötige 5 Nachkomma stellen.In gewissen fällen, wird es korrekt gerechnet. Dann wieder nicht.
-
Deforation schrieb:
In gewissen fällen, wird es korrekt gerechnet. Dann wieder nicht.
Da ist mir nicht ganz klar, was du meinst.
Der oben dargestellte Fall muss funktionieren. Der Unterschied ist bei einer einfachen Subtraktion viel zu groß um mit "Fließkomma Genauigkeit" erklärt zu werden.
Mit welchem System arbeitest du?
Wie groß ist double bei dir? sizeof(double)
Wie gibst du das Ergebnis aus bzw. woher stammt die Zahl 6.6678246842E-05?Also, etwas mehr Informationen (Programmcode) würden helfen.
-
Deforation schrieb:
In gewissen fällen, wird es korrekt gerechnet. Dann wieder nicht.Da ist mir nicht ganz klar, was du meinst.
Zur Erläuterung:
Ich möchte habe einen Countdown programmiert (in einem separaten Thread).Dort ermittle ich stets die Differenz zwischen dem Zieldatum + Zeit zum jetzigen Datum + Zeit.
Anschliessend werden diese umgerechnet und als Tage:Stunden:Minuten:Sekunden dargestellt.Problem:
Die Rechnung funktioniert stets, ausser wenn die Differenz zwischen den Zeiten 0-9 Sekunden beträgt. Denn dann erhalte ich "komische" Werte.
Wie z.B. 275:547:12:5
was ich darauf zurückführen konnte, dass die Subtraktion ein ungenaues Resultat zurückgibt.Hier ein Auszug aus dem Code (Geschrieben mit dem C++Builder 2009):
double dDateVal1; double dDateVal2; double dCurrentVal; double dSeconds; double dMinutes; double dHours; double dDays; UnicodeString sValue; UnicodeString sDays; UnicodeString sHours; UnicodeString sMinutes; UnicodeString sSeconds; UnicodeString sNegative; dDateVal1=StrToDateTime(tdSynchroDate.FormatString("dd.mm.yyyy") + " " + ttSynchroTime.FormatString("HH:NN") + ":01",*tfsDateFormat).Val; dDateVal2=Date().CurrentDateTime().Val; dCurrentVal=dDateVal1 - dDateVal2; if ((i==0 && tdSynchroDateFeiertag.Val!=0) || i==1) { if (dCurrentVal<0){ sNegative="- "; dCurrentVal*=-1; } else sNegative=""; //Berechnen der Teilzeiten sValue=FloatToStr(dCurrentVal,*tfsDateFormat); //Generieren der Zeitangaben //Ermitteln der Tage dDays=StrToFloat(sValue.SubString(0,sValue.Pos(".")-1),*tfsDateFormat); //Ermitteln der Stunden dCurrentVal-=dDays; dCurrentVal=(dCurrentVal<0) ? dCurrentVal*-1 : dCurrentVal; dCurrentVal*=24; sValue=FloatToStr(dCurrentVal,*tfsDateFormat); dHours=StrToFloat(sValue.SubString(0,sValue.Pos(".")-1),*tfsDateFormat); //Ermitteln der Minuten dCurrentVal-=dHours; dCurrentVal=(dCurrentVal<0) ? dCurrentVal*-1 : dCurrentVal; dCurrentVal*=60; sValue=FloatToStr(dCurrentVal,*tfsDateFormat); dMinutes=StrToFloat(sValue.SubString(0,sValue.Pos(".")-1),*tfsDateFormat); //Ermitteln der Sekunden dCurrentVal-=dMinutes; dCurrentVal=(dCurrentVal<0) ? dCurrentVal*-1 : dCurrentVal; dCurrentVal*=60; sValue=FloatToStr(dCurrentVal,*tfsDateFormat); dSeconds=StrToFloat(sValue.SubString(0,sValue.Pos(".")-1),*tfsDateFormat); //Formatieren, so das jedes Glied mindestens 2 Stellen hat sDays=FloatToStr(dDays,*tfsDateFormat); sHours=FloatToStr(dHours,*tfsDateFormat); sMinutes=FloatToStr(dMinutes,*tfsDateFormat); sSeconds=FloatToStr(dSeconds,*tfsDateFormat); if (sDays.Length()==1) sDays="0" + sDays; if (sHours.Length()==1) sHours="0" + sHours; if (sMinutes.Length()==1) sMinutes="0" + sMinutes; if (sSeconds.Length()==1) sSeconds="0" + sSeconds; //Setzen der Zeitangaben if (i==0) lblCountdownFeiertag->Caption=sNegative + sDays + ":" + sHours+ ":" + sMinutes + ":" + sSeconds; else if (i==1) lblCountdownTask->Caption=sNegative + sDays + ":" + sHours+ ":" + sMinutes + ":" + sSeconds;Nun zu den Fragen:
Mit welchem System arbeitest du?
- Windows XP 32-Bit Service Pack 3
- C++ Builder 2009Wie groß ist double bei dir? sizeof(double)?
- double ==> 8Wie gibst du das Ergebnis aus bzw. woher stammt die Zahl 6.6678246842E-05?
- Diese Zahl wird mir beim Debugger angezeigt (in der Liste der Lokalen Variabeln)
- Ist der Wert "genau", so wird dieser dort auch richtig angezeigt
. Ist er eben nicht genau, wird ein Wert wie oben beschrieben angezeigt.Wichtig:
Ich habe die Datumswerte auch auf 5 Kommastellen gekürzt. (Diese Anzahl brauche ich ja, da 1 = 1 Tag ist und ich das ganze Sekundengenau benötige.Ich hoffe, ich konnte mit diesen Informationen mein Problem ein wenig verdeutlichen.
-
Deforation schrieb:
...
1. Befindest du dich im ANSI C++ Forum, dein Code ist jedenfalls VCL
2. Solltest du dir mal die Hilfsfunktionen für Datum/Zeit/Zeitabstände... in der VCL betrachten, ich bin mir Sicher, das dein Code sich a) lesbarer und b) deutlich kürzer schreiben kannst.
3. Sollte es dich selbst schon zum Nachdenken bringen wie viele Konvertierungen du im Code durchführst.cu André
-
Deforation schrieb:
Ich hoffe, ich konnte mit diesen Informationen mein Problem ein wenig verdeutlichen.
Also, ich versuche erst gar nicht da durchzusteigen und werfe das Handtuch.

Es ist jedenfalls deutlich mehr involviert als die Subtraktion in deinem ersten Beitrag, das wird jetzt klar.
-
Es sieht in jedem Fall so aus, als würdest ein paar mehr Operationen mit deinen Variablen durchführen, als nur eine Subtraktion.
Hast du mal konstant überprüft, wie sich deine Variablen während deinem Programm verändern und welchen Fehler du damit mitschleppst, oder beziehen sich deine Werte auf "die schieb ich oben rein und das kommt unten raus"?
Im zweiten Fall wird deine Ungenauigkeit schlichtweg durch das Ausführen von sehr vielen Gleitkommaoperationen hintereinander immer größer..
Sieh dir mal die einzelnen Subtraktionen separat an, dann wirst du, denke ich, sehen, wie da kleine Ungenauigkeiten immer größer werden.

-
Das Problem tritt bei folgenden Zeilen auf:
dDateVal1=StrToDateTime(tdSynchroDate.FormatString("dd.mm.yyyy") + " " + ttSynchroTime.FormatString("HH:NN") + ":01",*tfsDateFormat).Val; dDateVal2=Date().CurrentDateTime().Val; dCurrentVal=dDateVal1 - dDateVal2;das heisst, dass dCurrentVal nun einen komischen Wert hat.
Und da wird noch nichts gemacht ausser eine Subtrakton.
-
Ich bin zu faul, mir den ganzen Code durchzulesen. Aber in der VCL kann man direkt mit Datums- / Zeitwerten rechnen. Siehe TDateTime.
Es gibt auch eine Funktion SecondsBetween(), mit der sich die Sekunden zwischen zwei Datumswerten ermitteln lassen. Da brauxhst Du dann gar keine double-Variablen mehr...