[gelöst] Problem mit 7-stelligen Float
-
Hallo,
ich bin noch Anfänger in der C++-Programmierung und versuche mir nach und nach mehr anzueignen.
Mein Problem:
Ich will ein 3D-Array mit float Werten erzeugen (Zeile 20), es befüllen (Zeile 26) als Ergebnis erhalte ich jedoch nur Integer (Ausgabe von Zeile 33).Mein Code:
#include <iostream> using namespace std; int main() { // spatial dimensions const int size = 20; const float grid_length = 1.0f; // [meter] // physical parameter const float T_s = 293.0f; // Surface temperature [K] // EDIT const float p0 = 101300.0f; // Surface pressure [Pa] // physical constants const float R = 287.058f; // gas constant [J / (K * kg) ] const float rho_0 = p0 / (R * T_s); // Density of air [kg / m³] const float g = 9.81f; // gravity acceleration [m / s²] // generate arrays of pressure float p[size][size][size]; // initialize the temperature and pressure: for (unsigned int x = 0; x < size; x++){ for (unsigned int y = 0; y < size; y++){ for (unsigned int z = 0; z < size; z++){ p[x][y][z] = p0 - rho_0 * g * grid_length * static_cast<float> (z); } } } // Output for (unsigned int i=0; i < size; i++){ std::cout << p[0][0][i] << "\n"; } }
Die Ausgabe enthält überraschender Weise jedoch keine Floats, sondern nur noch Integer (sie wird quasi nach der 6. Stelle abgeschnitten):
101300 101288 101276 101265 ...
Mein System / Compiler:
Ubuntu 14.04 / g++ Version 4.8.2Meine eigene Einschätzung war zuerst, dass ich eventuell einen Speicherüberlauf habe oder einen Casting-Fehler mache, jedoch müssten doch normale floats für diesen Wertebereich ausreichen, oder? Mit Google ließ sich nichts finden, da es dort meist nur um das gewollte "Abschneiden" von Nachkommazahlen ging.
Was mache ich falsch?
// EDIT: Deklaration von T_s eingefügt, um Beispiel kompilierfähig zu machen
-
Was ist T_s?
Welches Ergebnis erwartest du?
-
Das Standardausgabeformat fuer Fliesskommazahlen sind Sechs signifikante Stellen.
Moechtest du mehr, dann ersetze Zeile 33 durch
std::cout << setprecision(8) << p[0][0][i] << "\n";
Das gibt dir bspw. Acht signifikante Stellen. Aber Vorsicht:
floats
sind natuerlich nicht praezise, und in der Groessenordnung von einer Million wird es schnell viele Annaeherungs-Ungenauigkeiten geben, wodurch die Nachkommastellen unbrauchbar werden.Aber hoere lieber nicht auf mich, ich habe von Fliesskommazahlen wenig Ahnung...
P.S.: Wer das Beispiel kompilieren will, muss in Zeile 16 den Initializer zu
1.225
aendern.
-
Vielen Dank!
Mit setprecision(8) und der Einbindung von "<iomanip>" funktioniert es!Tut mir leid, beim Kopieren ist mir eine Zeile entfallen, habe es nachträglich editiert.
-
"Funktioniert es" - Was funktioniert jetzt und was ging vorher nicht? Was ist denn jetzt anders? Hast du verstanden was passiert ist? Gibt es vllt einen Grund dass standartmäßig auf sechs stellen (bei der Darstellung der Zahl) gerundet wird?
http://ideone.com/WIiGgMIst jetzt eigentlich irgendwas anders an dem Programm?
Fragen über Fragen.
-
Ich hatte nicht verstanden warum die Ausgabe nur 6 Stellen anzeigt und hatte an einen Casting- oder Überlauffehler gedacht. Arcoth hat mir aber freundlicherweise gezeigt, dass es nur an dem Standardausgabeformat lag, welches man mithilfe von setprecision() aus der Klasse "<iomanip>" ändern kann. Dementsprechend "funktioniert es" jetzt.
Letztendlich hat sich mit dem Befehl an dem Programm und den berechneten Werten ja nichts geändert, sondern nur die Ausgabe. Warum die Standardausgabe nur 6 Stellen ausgibt, weiß ich nicht. Definitionssache?
-
Ich versuch mal, das ganze mit ein bisschen mehr Details zu füllen. Das grundsätzliche Problem im Umgang mit reellen Zahlen ist, dass es sehr viel mehr reelle Zahlen gibt, als auch der größte Computer speichern könnte, und dass selbst das noch mehr sehr, sehr viel mehr ist, als man mit einem Computer sinnvoll handhaben kann. Es gibt zwei Arten, mit der Misere umzugehen -- fixed point und floating point.
Fixed Point (nur kurz umrissen) bedeutet, dass man mit Brüchen rechnet, die alle den gleichen Nenner haben. Das bietet sich beispielsweise für bestimmte Anwendungen an, die mit Geldbeträgen arbeiten, indem man halt alles als Hundertstel auffasst.
Floating Point (das, was du benutzt) bedeutet, dass man praktisch mit wissenschaftlicher Notation arbeitet. Dezimal schreibt man zum Beispiel
1.234567e6 = 1.234567 * 106
um 1234567 darzustellen. "Floating Point" heißt das deshalb, weil die Stelle des Dezimalpunktes in der wissenschaftlichen Darstellung je nach Exponent in der ausgeschriebenen Darstellung hin- und herschwebt. Dabei nennt man die 1.234567 die Mantisse und die 6 den Exponenten.
Da der Speicherplatz begrenzt ist, ist diese Rechenweise nur mit bestimmten Einschränkungen durchführbar -- die Länge der Mantisse und des Exponenten ist begrenzt. Ich postuliere als Beispiel mal einen dezimalen Fließkommatyp mit einer siebenstelligen Mantisse und einem zweistelligen Exponenten. Das bedeutet, die (betragsmäßig) größte Zahl, die der Typ darstellen kann, ist
9.999999e99
und die kleinste ist
0.000001e-99
(Ich ignoriere hier ein paar Eigenheiten der üblichen Standards -- die Behandlung von Vorzeichen und subnormalen Zahlen -- um die Erklärung einfach zu halten. Nur, falls hier jemand mitliest, der das alles auch im Detail kennt und sich über meinen Mangel an Pedanterie aufregt.)
Sofort ergeben sich eine ganze Reihe Probleme:
1. Genauigkeit
Da die Mantisse begrenzt ist, können Zahlen mit vielen signifikanten Stellen nicht genau dargestellt werden. So passen zum Beispiel 1.2345678, 123.45678 und 12345678 nicht in den Typen hinein. Ihre Darstellung wäre
1.2345678e0
1.2345678e2
1.2345678e6...aber 8 Stellen Mantisse haben wir halt nicht zum Speichern, und so würde gerundet. Dabei käme dann
1.234568e0
1.234568e2
1.234568e6als tatsächlicher Wert heraus, also 1.234568, 123.4568 und 12345680.
2. Die üblichen Rechengesetze gelten nicht mehr.
Beispiel: Mit echten reellen Zahlen ist 1234567 + 0.1 = 1234567.1, und 1234567.1 ist mit unserem Typen nicht darstellbar. Es ergibt sich eine Situation, in der y != 0 und x + y == x.
Beispiel Assoziativität: (0.4 + 0.4) + 1234567 = 0.4 + (0.4 + 1234567) <=> 1234568 = 1234567
Auf ähnliche Weise werden alle Rechenarten beschädigt. Man spricht hier von Rundungsfehlern, die sich daraus ergeben, dass Zwischenergebnisse und Ergebnisse von Berechnungen nicht genau speicherbar sind. Häufig kommt man dabei in der ungefähren Umgebung des tatsächlichen Ergebnisses raus, aber es gibt durchaus Zusammenhänge, in denen das nicht der Fall ist. Zum Beispiel
3. Vergleiche
Ist (1 / 3) * 3 == 1?
Nun:
1 / 3 = 3.333333e-1 = 0.3333333
(1 / 3) * 3 = 0.3333333 * 3 = 0.9999999...nein. Man behilft sich daher in der Regel mit Epsilon-Vergleichen. x ist etwa gleich y, wenn |x - y| < ε, wobei ε eine passend gewählte, kleine Zahl ist (gerne von x oder y abhängig, etwa |x * 10-6|).
4. Auslöschung
Worin sich in der Differenz aus zwei berechneten, ungenauen Werten der noch signifikante Anteil weghebt und nur noch der Rundungsfehler übrig bleibt. Beispiel:
(1234567 + 0.5) - (1234567 + 0.499) = 1234568 - 1234567 = 1
Als wäre all das noch nicht genug, rechnen Computer binär statt dezimal. Das ist im Grunde nicht weiter schlimm -- mathematisch sind die beiden Näherungen etwa gleich gut -- es führt aber dazu, dass die meisten Zahlen, die wir als Menschen der Maschine vorwerfen, von dieser genausowenig genau dargestellt werden können wie 1/3 in dezimaler Notation. Gleichgültig, wie groß ein binärer Fließkommatyp ist, 0.1 wird er nie genau darstellen können.
Soweit die Theorie. Was bedeutet das für deinen Code? Nun, von den kleineren Rundungsfehlern mal abgesehen, hast du in
p0 - rho_0 * g * grid_length * static_cast<float> (z)
eine Differenz zwischen zwei Zahlen in doch recht unterschiedlichen Größenordnungen -- sie unterscheiden sich anscheinend so um Faktor 10000, also säbelst du dir vier dezimale, signifikante Stellen schonmal weg. Dummerweise hat ein handelsüblicher float nach ieee-754 nur 7.22 dezimale, signifikante Stellen (24 binäre), so dass dir noch gut drei übrigbleiben. Wenn du jetzt eine Zahl in der Größenordnung von 105 auf 8 Nachkommastellen ausgeben lässt, die 7 signifikante Stellen hat, von denen 3 aus deiner Rechnung stammen, wie verlässlich wird wohl das Ergebnis sein?
Wenn du jetzt all deine floats in doubles umwandelst, sieht die Lage deutlich weniger angespannt aus -- der hat 53 binäre Stellen, was umgerechnet etwa 16 dezimale sind. Ich lege dir also dringend ans Herz, das zu tun.
-
Wow, Danke erstmal für deinen ausführlichen Kommentar
Ich hatte zwar schon von der Ungenauigkeit der Floats gehört, aber eigentlich gedacht, dass es bei meinen kleinen Zahlen noch nicht wirklich beachtenswert ist. Aber du hast natürlich recht, ich habe eine relativ große Zahl, deren geringen Abweichung ich berechnen will. Wenn ich nun so große Rundungsfehler in Kauf nehme, kann ich die Zahlen auch gleich würfeln.
Ich werde deine Antwort beherzigen, nochmal vielen Dank!
Noch zu 3) Gibt es in C++ Module oder Klassen, die diese Vergleichsoperatoren (also ungefähr gleich, innerhalb der Epsilon-Umgebung) bereitstellen? Also quasi in Pseudocode:
A = 2.5 B = 2.4 Mein_Epsilon = 0.1 if(is_equal(A,B)){ cout << "Stimmt schon, Pi mal Daumen..."; }
-
Ist (1 / 3) * 3 == 1?
Nun:
1 / 3 = 3.333333e-1 = 0.3333333
(1 / 3) * 3 = 0.3333333 * 3 = 0.9999999...nein
1/3 wird bei float, welches eine Mantisse von 23 Bit hat, folgendermaßen dargestellt: M = 2796203 (am nähesten dran an ), der Exponent e ist -2.
Die eigentliche Mantisse ist . Aber die Darstellung der Mantisse ist nicht präzise, und damit wird die Mantisse als dargestellt, was etwa ist; das Endergebnis ist
Was auch genau das Ergebnis vonstd::cout << std::setprecision(10) << 1/3.f << '\n';
ist.
...nein
Das hängt wiederum davon ab, wie dargestellt wird. Auf meiner Maschine zum Beispiel ist das mit
3.f
statt3
(Was im Übrigen auch3.
bzw.3.0
sein sollte) wahr. Weil eben das Dreifache von1/3.0
oder1/3.0f
am genauesten durch1
dargestellt wird.
-
Ich habe von einem hypothetischen Dezimalfloat her argumentiert, bei dem das definiert ist.