Problem: Casten von double in long
-
Hi Leute,
ich habe folgenden Quelltext geschrieben:
#include <stdio.h> class TBruch { private: long FZaehler, FNenner; public: TBruch(); TBruch(long); TBruch(long, long); TBruch(double); void Kuerze(); void Ausgabe(); }; void main() { TBruch A(3.131); A.Kuerze(); A.Ausgabe(); } TBruch::TBruch() { FZaehler = 0; FNenner = 1; } TBruch::TBruch(long Zaehler) { FZaehler = Zaehler; FNenner = 1; } TBruch::TBruch(long Zaehler, long Nenner) { FZaehler = Zaehler; FNenner = Nenner; } TBruch::TBruch(double Bruch) { long i = 10; while (Bruch != long(Bruch)) { Bruch *= 10; i *= 10; } FZaehler = long(Bruch); FNenner = i; Kuerze(); } void TBruch::Kuerze() { int i; if (FZaehler > FNenner) { for (i = FNenner; i > 1; i --) { if (((FNenner % i) == 0) && ((FZaehler % i) == 0)) { FNenner /= i; FZaehler /= i; } } } else { for (i = FZaehler; i > 1; i--) { if (((FNenner % i) == 0) && ((FZaehler % i) == 0)) { FNenner /= i; FZaehler /= i; } } } } void TBruch::Ausgabe() { printf("%d / %d\n", FZaehler, FNenner); }
Dabei entsteht das Problem, dass sich die while-Schleife in der Funktion TBruch::TBruch(double Bruch) ins Nirvana verabschiedet. Woran kann das liegen, obwohl nach dem dritten Durchgang 3131.000000... mit 3131 gleich zu sein scheint, wenn man sich die Werte beim Debuggen anschaut. Das Erstaunliche dabei ist, dass es mit manchen Zahlen funktioniert und mit manchen nicht (egal, wie viele Stellen sie nach dem Komma haben)
-
Ich könnte wieder die Gebetsmühle rausholen...
Fließkommatypen sind ungenau. Die meisten Dezimalbrüche sind nicht ohne Fehler als double oder float darstellbar, daher wird intern gerundet. Es kann also durchaus sein, dass du durch fortgesetztes Multiplizieren mit 10 nicht genau auf eine ganze Zahl kommst.
Du könntest prüfen, ob der Betrag der Differenz zwischen dem double und dem long unter einer bestimmten Schwelle liegt.
-
Nachtrag:
Ich gebe auch zu bedenken, dass i lange den Wertebereich eines long überschritten haben kann, bevor der double auch nur in die Nähe von eins kommt.
-
Ist wohl mehr ein C++-Problem.
--> verschoben ins C++-Forum
-
Sorry, aber das hat uns unser Programmier-Lehrer halt so als Aufgabe gegeben (jaa, mit Fließkommazahlen. ) Ok, ich werd versuchen, es anders zu lösen.
Aber dass, die Zahlen so ungenau sind, dass es schon bei 4 Nachkommastellen zu Ungenauigkeiten kommt??? Was für'n Mist...
-
Fließkommazahlen sind u.U. sehr genau, nur kann es aufgrund der Darstellung im Speicher (Mantisse und Exponent) bei Rechenoperationen zu starken Ungenauigkeiten kommen, da der Exponent erst gleichgesetzt werden muß.
Eventuell mal nach numerischer Mathematik googlen - die beschäftigt sich mit dieser Problematik.
-
Hab ne schöne Lösung gefunden:
TBruch::TBruch(double Bruch) { std::ostringstream temp; temp.precision(std::numeric_limits<double>::digits10); temp << Bruch; std::string str = temp.str(); std::string::size_type p = str.find("."); if(p == std::string::npos) FNenner = static_cast<long>(Bruch); else { FNenner = static_cast<long>(pow(10, static_cast<int>(str.size() - ++p))); FZaehler = static_cast<long>(Bruch * FNenner); } Kuerze(); }
Bei dieser Variante musst du allerdings noch einiges includen:
#include <sstream> #include <limits> #include <cmath>
Hier nimmt man in C++ btw. cstdio anstatt stdio.h
#include <stdio.h>
-
Und der Titel ist irrefuehrend !!!
(Bruch != long(Bruch))
// Umwandeln: benutzt die standard umrechnungen
long Lx = 0;
double dx = 12.345;
lx = dx; // lx ist nun 12 !// casten: = interpretieren
// 1. problem long = 4byte double = 8byte ->aua
// trotzdem bekommt mans hin !
long Lx = 0;
double dx = 12.345;
lx = (long)dx; // boeser boeser C cast
lx = reinterpret_cast<long>(dx); // wird dir zumindest ne warnung ausspucken, da die Groessen ungleich ...Preifrage, wie gross ist lx nun ???
Ich mags ned aussrechnen !!!
zuweisung nehm ich, wenn ich nen typ mathemathisch korrekt umwandeln will. also die werte nach vordefinierten regeln angepasst werden. flieskomma -> integer, nachkommastellen werden abgeschnitten.
Casts nehm ich, wenn ich werte in anderen typen verstecken will.
nen int von groesser auf kleiner und umgekehrt ist noch ueberschaubar. bei groesser auf kleiner, wird der Anteil der ueber steht abgeschnitten 8byte -> 4byte = 0x1234567800000001 => 00000001;
bei flieskomma zu integer und flieskommas untereinander mit unterschiedlicher groesse, musst den internen aufbau der flieskommas kennen, dann kannst errechnen, was bei rauskommt ....Ciao ...
-
Ich hab jetzt herausgefunden, wo genau der Fehler liegt.
41: while (Bruch != long(Bruch)) { 004011C4 fld qword ptr [ebp+8] 004011C7 call __ftol (004014b8) 004011CC mov dword ptr [ebp-0Ch],eax 004011CF fild dword ptr [ebp-0Ch] 004011D2 fcomp qword ptr [ebp+8] 004011D5 fnstsw ax 004011D7 test ah,40h 004011DA jne TBruch::TBruch+53h (004011f3) 42: Bruch *= 10; 004011DC fld qword ptr [ebp+8] 004011DF fmul qword ptr [__real@8@4002a000000000000000 (00426020)] 004011E5 fstp qword ptr [ebp+8] 43: i *= 10; 004011E8 mov eax,dword ptr [ebp-8] 004011EB imul eax,eax,0Ah 004011EE mov dword ptr [ebp-8],eax 44: } 004011F1 jmp TBruch::TBruch+24h (004011c4) 45: FZaehler = long(Bruch); 004011F3 fld qword ptr [ebp+8] 004011F6 call __ftol (004014b8) 004011FB mov ecx,dword ptr [ebp-4] 004011FE mov dword ptr [ecx],eax 46: FNenner = i; 00401200 mov edx,dword ptr [ebp-4] 00401203 mov eax,dword ptr [ebp-8] 00401206 mov dword ptr [edx+4],eax 47: Kuerze(); 00401209 mov ecx,dword ptr [ebp-4] 0040120C call @ILT+0(TBruch::Kuerze) (00401005) 48: }
Der Befehl fld lädt hier den Wert irgendwie falsch gerundet (?) ins Float-Register. (das meinte wohl Cocaine...)
Also hab ich die Abfrage etwas geändert:
TBruch::TBruch(double Bruch) { long i = 1; while ((Bruch * i) != long(Bruch * i)) { i *= 10; } FZaehler = long(Bruch * i); FNenner = i; Kuerze(); }
Dadurch werden die Werte ja direkt innerhalb der Float-Register verglichen.
-
Das hat nichts mir Float-Registern zu tun.
Dein neuer Code läuft besser, weil du nicht mehr mit dem gerundeten Ergebnis aus dem letzten Schleifendurchlauf weiterarbeitest, sondern den Wert jedesmal neu berechnest und sich der Rundungsfehler so nicht verstärken kann.
Du hast damit den Fehler nicht behoben, er wird nur seltener auftreten.
Verwende mal 2 statt 10.
-
MFK schrieb:
Das hat nichts mir Float-Registern zu tun.
Und wieso steht dann beim ersten Durchlauf +3.13099999999999978e+0000 im Float-Register!? Die Zahl, die ich benutze ist doch 3.131!!!
Wieso sollte ich mit 2 multiplizieren, wenn ich die Kommastelle verschieben will? (Ja, nur zum Testen, ich versteh schon...)
-
Stevie schrieb:
MFK schrieb:
Das hat nichts mir Float-Registern zu tun.
Und wieso steht dann beim ersten Durchlauf +3.13099999999999978e+0000 im Float-Register!?
Weil genau diese Zahl ins FPU-Register geladen wird. Die steht vorher schon so im Speicher (bei [ebp+8]). Der Grund ist schlicht und einfach, dass im Binärsystem nur Brüche, die sich aus Zweierpotenzen zusammensetzen, nicht-periodisch sind (1/2, 1/4, 1/8 usw.).
-
Soll das heißen, dass garnicht erst 3.131 gespeichert wird, sondern +3.13099999999999978e+0000? Dann würde ein Vergleich von 3.131 und +3.13099999999999978e+0000 also true ergeben, oder wie?
Hab's ausprobiert...
(+3.13099999999999978e+0000 == 3.131)
ergibt true!!!
-
Jo, viele "harmlose" Zahlen haben eine periodische Binärdarstellung, z.b. 0,1 = (binär)0,00011001100110011...
-
Boah, dass das sooo übel ist, hätte ich ja nie vermutet! Wie wird das denn gemacht, wenn es wirklich wichtig ist, dass die Zahlen genau sind? Da muss man doch dann andere Datentypen benutzen, oder?
-
Absolute Genauigkeit gibt es nicht, am nächsten kommst du dem mit symbolischer Mathematik ala Maple, höllisch aufwendig, dafür ist sqrt(x^2) aber exakt x. Kommst du mit den Grundrechenarten aus, bieten sich rationale Zahlen mit Bignum-Arithmetik an, d.h. Zähler und Nenner werden getrennt geführt und können jeweils beliebig groß sein. Einige Sprachen haben sowas eingebaut (Common Lisp z.B.). Brauchst du Genauigkeit auf einige Stellen von Dezimalzahlen, z.b. für Finanzberechnungen, dann gibt es in einigen Sprachen einen speziellen decimal-Typ dafür. Ich glaub Cobol und C# (bzw. .net) haben sowas.
Die ganzen Sachen kann man sich natürlich auch selbst als Klassen bauen, wenn man Lust hat