Bild einer Blume

Computer rechnen anders!

Wer rechnet, ist immer in Gefahr, sich zu verrechnen - Theodor Fontane

Software
Opera-Tool
Mbox merge
Mbox Import
Benfords Law

Eclipse
Eclipse und C/C++

Office
Excel Balkengrafik
Excel Farben

Artikel
Computer rechnen anders!
Impressum

Computer rechnen anders?

In Newsgruppen findet man immer wieder Fragen, die sich um das Thema "ungenaue Rechenergebnisse" drehen.

Zum Beweis, wie ungenau der Computer rechnet, werden Beispiele angeführt:

0.2 + 0.7 ergibt 0.89999999
1.234 * 1000 ergibt 1233.999967

Woher kommen diese Ungenauigkeiten???

Dieser Artikel soll dem Programmiereinsteiger in das Thema einführen und Hintergrundinformationen bieten, um diese Probleme zu verstehen und dadurch zu vermeiden.

Dezimalzahlen und Dezimalbrüche

Dezimalzahlen und ihre Schreibweise haben wir alle in der Schule gepaukt. Durch die tägliche Verwendung nehmen wir gar nicht mehr wahr, das eine recht ausgetüftelte Schreibweise zur Darstellung der Dezimalzahlen verwendet wird, das sogenannte Positionssystem.

In einem Positionssystem erhalten die Ziffern außer ihrem Zahlenwert noch einen Stellenwert. Den Stellenwert drücken wir in unsere Sprache aus, indem wir von Hunderter, Zehner, Einer usw. sprechen.

Für jede Ziffer ist es also sehr wichtig, an welcher Stelle sie steht, denn dadurch wird Ihr Stellenwert bestimmt. Der Stellenwert wächst von rechts nach links, die Bezugsziffer ist die Einerstelle, jede Ziffer links davon hat eine um den Faktor 10 höhere Wertigkeit pro Stelle.

Normalerweise schreiben wir den Stellenwert nicht explizit hin, das wäre Platz und Zeitverschwendung, aber zu Verdeutlichung wollen wir hier ein Beispiel mit Angabe des Stellenwertes ansehen:

Beispiel: 1204 entspricht ausgeschrieben: 1*1000 + 2*100 + 0*10 + 4*1

Im Positionssystem bewährt sich auch die Null hervorragend. Sie belegt eine Position mit dem Zahlenwert 0, sorgt aber dafür, das die weiter links oder rechts stehenden Ziffern an der richtigen Position stehen. Ohne Null (oder etwas gleichwertiges) könnten wir unser Positionssystem gar nicht verwenden.

Das Positionssystem war ein großer Fortschritt gegenüber anderen Systemen wie z.B. dem römischen Additionssystem. Das schriftliche Rechnen ist im Positionssystem viel einfacher und Dezimalzahlen lassen sich kompakter schreiben als römische Zahlen (Beispiel: 7 =  VII,  88 = LXXXVIII).

Doch zurück zu den Dezimalzahlen. Bisher können wir im gerade vorgestelltem Positionssystem nur ganze Zahlen notieren. Ein weitere wichtiger Schritt war es daher, diese Schreibweise so zu erweitern, das auch Brüche und irrationale Zahlen darstellbar sind.

Jeder beliebige Bruch lässt sich in einen Dezimalbruch (Brüche mit Nenner gleich 10, 100, 1000 usw.) umrechnen.

Wenn wir Dezimalbrüche verwenden, können wir das Schema des Dezimal-Positionssystems - jeder Stelle unterscheidet sich vom Nachbar um den Faktor 10 - auch nach rechts zu kleineren Stellen fortsetzen und wir erhalten rechts von der Einerstelle die Zehntel, Hundertstel, Tausendstel usw.

Wenn wir unser Beispiel von oben erweitern, sagen wir um den Bruch 1/4, müssen wir 1/4 zuerst in einen Dezimalbruch umwandeln:

 1     1*25     25      2      5
——— = —————— = ———— = ———— + —————
 4     4*25    100     10     100

Das können wir dann als Dezimalzahl schreiben als:

1204,25

Das Komma müssen wir setzen, damit wir die Einerstelle erkennen können.
Allerdings werde ich hier kein Komma mehr verwenden, sondern als Trennzeichen den Punkt, so wie das in Programmiersprachen (und einigen natürlichen Sprachen) üblich ist. Unser Zahlenbeispiel, mit Angabe aller Stellenwerte, sieht dann so aus:

                                           1         1
1204.25 = 1*1000 + 2*100 + 0*10 + 4*1 + 2*———— + 5*—————
                                           10       100

Die uns so vertraute Schreibweise mit den Dezimalbrüchen wurde in Europa erst etwa um 1600 von dem niederländischer Mathematiker und Ingenieur Simon Stevin (Wikipedia über Simon Stevin) verbreitet.

Kann man mit Dezimalzahlen alle Brüche und alle irrationalen Zahlen darstellen?

Wenn wir versuchen, so harmlose Brüche wie 1/3 oder 1/7 in einen Dezimalbruch umzuwandeln, stellen wir fest, das sich Ziffern oder Ziffernfolgen zu kleineren Stellen hin ständig wiederholen:

1/3 -> 0.333333333333333333333333
1/7 -> 0.142857142857142857142857

Solche Brüche nennt man periodisch, da sich die Ziffern(folgen) unendlich oft wiederholen.

Irrationale Zahlen lassen sich nicht als Bruch darstellen und sie haben eine unbegrenzte Stellenanzahl; bekannte Beispiele für irrationale Zahlen sind die Kreiszahl π und √2. (weitere Infos zur Zahl π).

Zur exakten Darstellung irrationaler Zahlen und der periodischen Dezimalbrüche müssten wir unendlich viele Stellen notieren. Das ist weder auf Papier noch im Computerspeicher möglich. Deshalb müssen wir uns auf eine bestimmte Anzahl von Stellen beschränken!

Durch die Beschränkung auf eine bestimmte Anzahl von Stellen können wir viele Zahlen (periodische Zahlen, irrationale Zahlen) nicht mehr exakt als Dezimalzahl darstellen und erhalten daher nur eine Nährungsdarstellung für diese Zahlen!

Nehmen wir für ein Zahlenbeispiel an, das wir uns auf 5 Nachkommastellen beschränken müssen:

Berechnung von

 1     1     2
——— + ——— = ———
 7     7     7

Die Addition der Zahlen als Brüche ist einfach und ergibt das exakte Ergebnis 2/7. Rechnen wir dagegen mit Dezimalzahl mit nur 5 Nachkommastellen, so erhalten wir:

   1         1        2
  ———   +   ———   =  ———
   7         7        7

0.14285 + 0.14285 = 0.28570

Hier weicht die 5. Nachkommastelle schon vom genauen 5 stelligem Ergebnis für 2/7 ab:

 2
——— = 0.28571
 7

Die mathematisch exakte Bruchgleichung ist als Dezimalzahl mit begrenzter Stellenzahl plötzlich nur noch ungefähr gleich, da sich die Summe der beiden Brüche in der letzten Dezimalstelle vom korrekten Wert für 2/7 unterscheidet!

Löst runden das Problem?

Kurz gesagt - Nein!

Mit der begrenzten Stellenanzahl können wir bestimmte Zahlen nicht exakt darstellen. Entweder ist die Zahl etwas zu klein oder etwas zu groß. Im obigen Beispiel haben wir nicht gerundet. Wenn wir 1/7 aufrunden, erhalten wir für das gleiche Beispiel folgende Rechnung:

0.14286 + 0.14286 = 0.28572

Wieder haben wir eine Abweichung in der letzten Stelle gegenüber der direkten Berechnung von 2/7!

Computer können nur bis Eins zählen

Alle (mir bekannten) Computer verwenden das sogenannte Binärsystem. Binär bedeutet hier "zwei unterscheidbare Werte" und der Grund für die weite Verbreitung dieses Systems ist seine einfache technische Realisierbarkeit und seine Zuverlässigkeit. Es ist relativ einfach, nur zwei verschiedene Zustände zu speichern (z.B. Flipflop-Schaltung) und solche Zustände fehlerfrei zwischen CPU und Speicher oder über externe Schnittstellen zu übertragen. Außerdem lassen sich Rechenwerke für Binärzahlen einfach in Hardware realisieren, da die binären Rechenregeln simpel sind.

Wenn wir im binären System nur 2 Zustände pro Stelle speichern können, dann müssen wir zur Darstellung größerer Zahlen als Ausgleich einfach mehr Stellen verwenden. Und da bietet sich wieder das schon bewährte Positionssystem an. Niemand verlangt, das es unbedingt Zehn als Basis für ein Positionssystem sein muss, so wie bei unserem Dezimalsystem.

Für ein Binärsystem verwenden wir daher die Zwei als Basis. Bei einer Binärzahl ändert sich also pro Stelle nach links die Wertigkeit um den Faktor 2, wenn man nach rechts geht, halbiert sich die Wertigkeit. Die Einerstelle ist wieder unsere Bezugsziffer und wird durch Komma oder Punkt gekennzeichnet. Hier ein Beispiel mit der Dezimalzahl 26.625, umgewandelt in eine Binärzahl:

                                                      1     1     1
26.625 = 11010.101 = 1*16 + 1*8 + 0*4 + 1*2 + 0*1 + 1*— + 0*— + 1*—
                                                      2     4     8

Bei den Dezimalzahlen haben wir festgestellt, das es Ungenauigkeiten geben kann, weil wir nur mit einer begrenzten Stellenzahl rechnen können. Natürlich treten diese Ungenauigkeiten auch bei Binärzahlen mit begrenzter Stellenzahl genauso auf.

Mein Computer zeigt aber Dezimalzahlen an

Alle Zahleneingaben und Ausgaben erfolgen (typisch) als Dezimalzahl. Das machen alle Programme, damit wir weiterhin unsere heißgeliebten Dezimalzahlen verwenden können und wir uns nicht direkt mit den Binärzahlen rumschlagen müssen. Bei allen Ein- und Ausgaben werden Zahlen also zwischen den Zahlenformaten "Dezimal" und "Binär" hin- und hergewandelt.

Intern werden die Zahlen aber typisch als Binärzahlen gespeichert und alle Rechnung erfolgen dann im Binärformat.

Ungenauigkeit durch Binärdarstellung

Wie wir oben schon gesehen haben, müssen wir die Stellenanzahl begrenzen und können daher weder alle Dezimal- noch Binärzahlen exakt speichern.

Wir haben auch gesehen, das sich manche Brüche nicht exakt als Dezimalbruch schreiben lassen, da sie periodisch sind.

Das gleiche Problem haben wir auch, wenn wir einen beliebigen Bruch in einen Binärbruch umwandeln, allerdings in noch etwas verschärfter Form! Einige Brüche, die sich noch exakt in einen Dezimalbruch umwandeln lassen, lassen sich nicht exakt in einen Binärbruch umwandeln!

Periodische Dezimalbrüche entstehen immer dann, wenn der Nenner des Bruchs andere Primfaktoren als 2 oder 5 enthält. Der Nenner eines Dezimalbruchs ist 10 (= 2*5), im Nenner des Dezimalbruchs sind also die Primfaktoren 2 und 5 enthalten.

Alle Brüche, deren Nenner keine anderen Primfaktoren als 2 und 5 enthalten, lassen sich immer in exakte Dezimalbrüche umwandeln (ausreichende Stellenanzahl vorausgesetzt).

Falls andere Primfaktoren enthalten sind und sich diese nicht wegkürzen lassen, ist keine exakte Umwandlung in einen Dezimalbruch möglich. Nun erkennen wir auch, warum wir solche Brüche wie 1/3 oder 1/7 nicht exakt in Dezimalbrüche umwandeln können, da 3 und 7 andere Primfaktoren sind!

Bei den Dezimalbrüchen haben wir im Nenner immerhin zwei Primfaktoren (2 und 5) und haben somit mehr Möglichkeiten, einen Bruch exakt in einen Dezimalbruch umzuwandeln, als dies bei Binärbrüchen möglich ist, da wir bei Binärbrüchen nur die 2 im Nenner haben!

Betrachten wir ein paar Zahlenbeispiele:

Dezimal
bruch
Dezimal
zahl
Binärzahl mit 5 binären Nach-
kommastellen
Binärzahl wieder als Dezimalzahl umgerechnet
5/10 0.5 Dieser Dezimalbruch ist exakt in einen Binärbruch umwandelbar, da wir 5/10 auf 1/2 kürzen können. 0.10000 0.5
1/10 0.1 Diese Zahl lässt sich nicht exakt in einen endlichen Binärbruch umwandeln, da wir den Primfaktor 5 nicht aus dem Nenner von 1/10 kürzen können. 0.00011 0.09375
2/10 0.2 Ebenso wie 1/10 nicht exakt als endlicher Binärbruch darstellbar. Man erkennt, von den einfachen Brüchen 1/10, 2/10, 3/10 bis 9/10 lässt sich nur 5/10 exakt als Binärbruch umwandeln, da sich die 5 im Zähler gegen den Primfaktor 5 im Nenner kürzen lässt. 0.00110 0.1875
25/100 0.25 Diese Zahl ist exakt als Binärbruch darstellbar, da sich der Primfaktor 5 kürzen lässt:
 25     5*5      1     0     1
——— = ——————— = ——— = ——— + ———
100   2*2*5*5   2*2    2     4
0.01000 0.25


Fassen wir die Ursachen für die Ungenauigkeiten zusammen:

Ursachen für Ungenauigkeiten:
  1. Beschränkung auf begrenzte Stellenanzahl
  2. Dezimalbrüche lassen sich nicht immer exakt in Binärbrüche umwandeln
  3. Ungünstige Algorithmen, Fehlerfortpflanzung etc. (Diese Themen wurden hier nicht behandelt!)

Wie vermeidet man Probleme in der Praxis?

Dieser Abschnitt gibt einige Hinweise für die Praxis. Allerdings werde ich mich kurz fassen da Sie ja nach Durcharbeitung des Artikel entsprechend vorsichtig und aufmerksam vorgehen werden :-)

  • Computer verwenden intern Binärzahlen. Dezimalbrüche lassen sich oft nicht exakt in Binärbrüche umrechnen. Diese beiden Punkte sollte man immer im Hinterkopf haben.
  • niemals zwei Float-Zahlen auf Gleichheit (oder Ungleichheit!) prüfen, besser auf größer oder kleiner
  • Rechenergebnis nicht exakt auf 0 (oder anderen Wert) prüfen, besser gegen Schwelle prüfen
  • Schwellen geeignet wählen, C bietet zB in float.h entsprechende Werte (FLT_EPSILON, DBL_EPSILON ...)
  • Bei der Multiplikation kleiner Zahlen mit großen Zahlen holen wir die Ungenauigkeit in die höheren Stellen der Zahl.
    Beispiel: 1.234 * 1000 = 1233.999967
    Schneiden wir die Nachkommastellen einfach ab, erhalten wir nicht das erwartet Ergebnis 1234, sondern 1233!
    Aufrunden scheint hier auf den ersten Blick zu helfen, den nach dem Runden würde 1234 herauskommen.
    Doch: 1.2345 * 1000 = 1234.50005, Aufrundung würde jetzt zu 1235 führen!
    Solche Fälle muss man vermeiden, am besten durch einen anderen Rechenwege, eventuell hilft auch die Verwendung von BCD oder Integer-Zahlen. In der Praxis verwendet man oft auch "Schutzstellen", d.h., man verwendet intern für die Rechnung mehr Stellen, als man ausgibt. Das löst aber die grundsätzlichen Probleme nicht, den auch mit Schutzstellen haben wir weiterhin nur eine begrenzte Anzahl von Stellen. Allerdings werden die Fehler unter Verwendung von mehr Stellen natürlich entsprechend kleiner.
  • Float-Zahlen nicht als Schleifenzähler verwenden, 10 * 0.1 führt z.B. nicht zu 1.0
  • Genauigkeit float (32 Bit) ca. 6 Dezimalstellen, Genauigkeit double (64 Bit) ca. 15 Dezimalstellen. Diese Genauigkeit reicht für viele Berechnung problemlos aus und das Programm wird auch problemlos laufen, wenn man die gerade erwähnten Regeln beachtet.
  • Finanzmathematik: Es ist vermutlich genau vorgeschrieben, wie viel Nachkommastellen mit welchen Algorithmen zu berechnen sind. Oft verwendet man hier BCD-Arithmetik (Zahlen werden als Dezimalzahlen gespeichert), eventuell auch selbst geschrieben Festkommaarithmetik oder spezielle Bibliotheken. Durch Verwendung einer Dezimal-Kodierung (BCD) kann man zwar die beschriebenen Umwandlungsfehler zwischen Dezimal- und Binärbrüchen vermeiden, aber alle anderen beschriebenen Ungenauigkeiten können trotzdem vorkommen.

Themen, auf die ich nicht weiter eingegangen bin:

  • ich habe nicht im Detail erklärt, wie man Brüche in Dezimalbrüche oder Binärbrüche umrechnet. Für die Erklärung der Probleme ist es nicht so wichtig und im Netz finden sich bestimmt entsprechende Texte unter dem Stichwort "Dezimalbrüche".
  • auf andere mögliche Rechenfehler, zB ungünstige Algorithmen, Rundungsfehler, Fehlerfortpflanzung etc. bin ich nicht weiter eingegangen.
  • die genaue interne Darstellung der Binärzahlen habe ich nicht betrachtet, sie ist für das Thema nicht weiter wichtig, wenn durchaus auch interessant. Wer mehr darüber wissen will, suche zB nach "IEEE float format" etc.
  • BCD-Arithmetik: Zahlen werden intern als Dezimalzahlen gespeichert und berechnet, die Ungenauigkeiten bei der Umwandlung von Dezimal nach Binär entfallen, aber die anderen Rechenfehler werden genauso auftreten.

Ich hoffe, der Artikel war hilfreich. Kommentare, Fehlerhinweise oder Verbesserungsvorschläge bitte an meine Email-Adresse senden.

Mailadresse als Bild (wegen Spam leider nötig...)
Top     Home Valid XHTML 1.0!