Frage

Geschweige,

float dt;

Ich lese dt aus einer Textdatei als

inputFile >> dt;

Dann habe ich eine for schleife als,

for (float time=dt; time<=maxTime; time+=dt)
{
    // some stuff
}

Wenn dt=0.05 und ich gebe aus std::cout << time << std::endl; Ich habe,

0.05
0.10
...
7.00001
7.05001
...

Also, warum steigt die Anzahl der Ziffern nach einer Weile?

War es hilfreich?

Lösung

Weil nicht jede Zahl durch IEEE754-Gleitkommawerte dargestellt werden kann.Irgendwann erhalten Sie eine Nummer, die nicht ganz darstellbar ist, und der Computer muss die nächstgelegene auswählen.

Wenn Sie 0,05 in eingeben Harald Schmidt's excellent online converter und verweisen Sie auf die Wikipedia-Eintrag zu IEEE754-1985, werden Sie mit den folgenden Bits enden (meine Erklärung dazu folgt):

   s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
   0 01111010 10011001100110011001101
     |||||||| |||||||||||||||||||||||
128 -+||||||| ||||||||||||||||||||||+- 1 / 8388608
 64 --+|||||| |||||||||||||||||||||+-- 1 / 4194304
 32 ---+||||| ||||||||||||||||||||+--- 1 / 2097152
 16 ----+|||| |||||||||||||||||||+---- 1 / 1048576
  8 -----+||| ||||||||||||||||||+----- 1 /  524288
  4 ------+|| |||||||||||||||||+------ 1 /  262144
  2 -------+| ||||||||||||||||+------- 1 /  131072
  1 --------+ |||||||||||||||+-------- 1 /   65536
              ||||||||||||||+--------- 1 /   32768
              |||||||||||||+---------- 1 /   16384
              ||||||||||||+----------- 1 /    8192
              |||||||||||+------------ 1 /    4096
              ||||||||||+------------- 1 /    2048
              |||||||||+-------------- 1 /    1024
              ||||||||+--------------- 1 /     512
              |||||||+---------------- 1 /     256
              ||||||+----------------- 1 /     128
              |||||+------------------ 1 /      64
              ||||+------------------- 1 /      32
              |||+-------------------- 1 /      16
              ||+--------------------- 1 /       8
              |+---------------------- 1 /       4
              +----------------------- 1 /       2

Das Vorzeichen 0 ist positiv.Der Exponent wird durch die Ein-Bit-Zuordnung zu den Zahlen auf der linken Seite angezeigt: 64+32+16+8+2 = 122 - 127 bias = -5, also ist der Multiplikator 2-5 oder 1/32.Der 127 bias soll die Darstellung sehr kleiner Zahlen ermöglichen (wie bei nahe Null eher als bei negativen Zahlen mit einer großen Größe).

Die Mantisse ist etwas komplizierter.Für jedes Ein-Bit akkumulieren Sie die Zahlen auf der rechten Seite (nachdem Sie ein implizites hinzugefügt haben 1).Daher können Sie die Zahl als Summe von berechnen {1, 1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}.

Wenn Sie all dies addieren, erhalten Sie 1.60000002384185791015625.

Wenn du multiplizierst dass durch den Multiplikator 1/32 (vorher aus den Exponentenbits berechnet), erhalten Sie 0.0500000001, also kannst du das sehen 0.05 is bereits nicht genau dargestellt.Dieses Bitmuster für die Mantisse ist eigentlich das gleiche wie 0.1 aber damit ist der Exponent -4 statt -5, und deshalb 0.1 + 0.1 + 0.1 ist selten gleich 0.3 (dies scheint eine beliebte Interviewfrage zu sein).

Wenn Sie anfangen, sie zu addieren, wird sich dieser kleine Fehler ansammeln, da Sie nicht nur einen Fehler in der 0.05 selbst, Fehler können auch in jeder Phase der Akkumulation eingeführt werden - nicht alle Zahlen 0.1, 0.15, 0.2 und so weiter kann auch genau dargestellt werden.

Schließlich werden die Fehler so groß, dass sie in der Nummer angezeigt werden, wenn Sie die Standardgenauigkeit verwenden.Sie können dies ein wenig verschieben, indem Sie Ihre eigene Präzision mit etwas wie wählen:

#include <iostream>
#include <iomanip>
:
std::cout << std::setprecison (2) << time << '\n';

Es wird die Variable nicht reparieren Wert, aber es wird Ihnen noch etwas Luft zum Atmen geben, bevor die Fehler sichtbar werden.

Nebenbei, Einige Leute empfehlen zu vermeiden std::endl da erzwingt es ein Spülen der Puffer.Wenn sich Ihre Implementierung von selbst verhält, geschieht dies für Endgeräte, wenn Sie trotzdem einen Zeilenumbruch senden.Und wenn Sie die Standardausgabe auf ein Nicht-Terminal umgeleitet haben, werden Sie wahrscheinlich nicht willst du in jeder Zeile spülen.Nicht wirklich relevant für Ihre Frage und es wird wahrscheinlich in den allermeisten Fällen keinen wirklichen Unterschied machen, nur ein Punkt, von dem ich dachte, ich würde ihn ansprechen.

Andere Tipps

IEEE-Floats verwenden das Binärzahlensystem und können daher Dezimalzahlen nicht genau speichern.Wenn Sie mehrere davon addieren (manchmal reichen nur zwei), können sich die Darstellungsfehler akkumulieren und sichtbar werden.

Einige Zahlen können mit Gleitkommazahlen ODER Basis-2-Zahlen nicht genau dargestellt werden.Wenn ich mich richtig erinnere, ist eine dieser Zahlen dezimal 0,05 (in Basis 2 ergibt sich unendlich Bruchzahl wiederholen).Ein weiteres Problem ist, dass Sie beim Drucken von Gleitkommazahlen in eine Datei (als Basis-10-Nummer) und beim Zurücklesen auch eine andere Nummer erhalten können - da sich die Basis unterscheidet und dies zu Problemen bei der Konvertierung von gebrochener Basis2 in gebrochene Basis10-Nummer führen kann.

Wenn Sie eine bessere Genauigkeit wünschen, können Sie versuchen, nach einer Bignum-Bibliothek zu suchen.Dies funktioniert jedoch viel langsamer als Gleitkommazahlen.Eine andere Möglichkeit, mit Präzisionsproblemen umzugehen, wäre der Versuch, Zahlen als "gemeinsamen Bruch" mit Numerator / Nenner zu speichern (dh.1/10 statt 0,1, 1/3 statt 0,333.., etc - es gibt wahrscheinlich sogar eine Bibliothek dafür, aber ich habe noch nichts davon gehört), aber das funktioniert nicht mit irrationalen Zahlen wie pi oder e.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top