Delphi Rechenfehler?

Hallo!

Bei einer Berechnung, bei der ich von einer Zahl (Extended)
eine andere Zahl (ebenfalls Extended) gibt es einen Rechenfehler:

z.B. Wenn ich zu -7.0 öfter 0.001 hinzuzähle erhalte ich irgendwann
zahlen wie z.B. -3.3099999999999.

Das ist extrem störend. Kann man da irgend etwas machen?

Danke!

Volker

Hallo,

Ursache

Grundsätzlich steht zur Speicherung von Zahlen im Hirn des Rechners nur ein bestimmter, endlicher Platz zu Verfügung. Üblicherweise werden Zahlen in einem Binärformat gespeichert. Dabei läßt sich nicht jeder Dezimalbruch (zB. 0.001) ohne Informations-Verlust codieren. Würde man zurückrechnen, ergäbe sich aus der binär codierten Dezimalzahl 0.001 vielleicht 0.001000000000024531… oder sowas, also eine ganz klein wenig andere Zahl. Wie groß diese Abweichung ist, hängt davon ab, wie viele Bit Speicher man der Binärdarstellung gönnt.

Beim Typ „Single“ sind das 32 Bit, „Double“ hat 64 Bit und „Extended“ genehmigt sich 80 Bit.

Zähle ich in einer Schleife von -7 in 0.001-Schritten bis auf 0 hoch (also 7000 Additionen), erhalte ich für

Extended: 1.3298E-16
Double: 6.72E-13
Single: 3.2499E-4

Alles recht knapp bei Null (was es theoretisch sein sollte), die Abweichung bei Single-Werte ist aber schon recht groß (0.0003 sind immerhin schon 30% des Betrages, der addiert wurde).

Das das wirklich an "Rundungsfehlern liegt, die durch die Binär-Codierung verursacht sind, kannst Du erkennen, wenn Du statt 0.001 mal eine Zahl nimmst, die sich verlustfrei binär codieren lässt. Ganz in der Nähe liegt die da Zahl 0.0009765625 bzw. 2^(-10). Wenn man das Selbe Spiel macht wie oben, bekommt man immer das exakte Ergebnis. Bis zur Null muss man hier sogar 7168 Additionen machen, weil die Zahl ja etwas kleiner ist als 0.001, aber bei jedem Typ - auch bei Single! - kommt dann exakt Null raus.

Lösung

Vermeide sequentielle Additionen (oder Subtraktionen). Wenn Du zB. 3000x den Wert 0.001 zu -7 addieren mußt, dann rechne -7 + 3000*0.001. Dabei kumulieren die Rundungsfehler nicht.

Stelle Ergebnisse immer nur bis zu einer brauchbaren Genauigkeit dar. Abweichungen von 1E-16 interessieren doch wirklich nicht.

Sollten sie doch interessieren, dann kannst Du versuchen, mit Ganzzahlen zu rechnen. In deinem Beispiel multiplizierst du beide Werte mit 1000, um daraus Ganzzahlen zu machen (zB. vom Typ Int64). Statt 0.001 zu -7 addieren, addierst du nun 1 zu -7000. Nach 7000 Additionen erhälst Du exakt Null - keine Rundungsfehler. Das „richtige“ Ergebnis ist natürlich dann immer die Summe geteilt durch 1000. Dieser Bruch hat dann wieder einen Rundungsfehler, aber eben nicht kumuliert.

LG
Jochen

Hallo!

Vielen Dank für deine Antwort!

Die erste Lösung (n-mal dazuzählen) hab ich schon probiert, trotzdem kommen hin und wieder rundungsfehler auf.

Die zweite Möglichkeit ist echt genial, so werd ichs machen!

Vielen Dank!

Volker

kleine Ergänzung…
zu dieser super ausführlichen Antwort:

Wer mit 4 Nachkommastellen auskommt soll sich auch den Typ Currency anschauen, das ist eigentlich ein 64Bit Integer, der im Grunde mit 1/1000 multipliziert wird. Solche Probleme wie bei den Floats kommen da nicht auf - allerdings ist man auf 4 Nachkommastellen beschränkt…

Grüße
Wolfgang