Homepage de.comp.lang.javascript
Homepage
de.comp.lang.javascript

FAQ de.comp.lang.javascript

FAQ
de.comp.lang.javascript

 

 

Bitte verwenden Sie als Einstiegsadresse in diese FAQ die Homepage der Newsgroup de.comp.lang.javascript. Der Aufenthaltsort dieser Seiten hier kann sich ohne Vorwarnung ändern.
 

 

Kann mein Browser nicht korrekt rechnen?

Inhalt dieser Seite:
Einleitung: Ungenaues Ergebnis einer einfachen Rechenoperation
Kleiner Exkurs: Dezimal- und Binärsystem
Wie kommt es zu den Rechenfehlern unter JavaScript?
Ungenaue Ergebnisse immer runden?
 
Inhalt der Seite "Zahlen runden und ausgeben":
Runden von Zahlenwerten für die Ausgabe
Kaufmännische Rundung
Dezimalkomma statt Dezimalpunkt
Null vor dem Komma ergänzen
 

Einleitung: Ungenaues Ergebnis einer einfachen Rechenoperation

Betrachten wir die folgenden Beispiele für ganz simple Additionen:

alert(0.3 + 0.6);   Test
alert(0.1 + 0.2);   Test

Wer zum Anschauen dieser Seite nicht einen sehr viel anderen Browser benutzt, als ich beim Schreiben, erhät bei einem Klick auf "Test" ganz offensichtlich ungenaue Ergebnisse: statt einer Nachkommastelle sechzehn oder siebzehn, mit einem Fehler in der jeweils letzten. Kann der Browser unter JavaScript also nicht korrekt rechnen?

Betrachten wir nun im Vergleich dazu diese Beispiele:

alert(0.3 + 0.1);   Test
alert(0.2 + 0.6);   Test

Ein Klick auf "Test" sollte hier die erwarteten Ergebnisse liefern. Es stellt sich die Frage, wieso bei so gleichartigen Rechnungen mal ein fehlerhaftes, mal ein korrektes Ergebnis herauskommt. Ein Bug?

Nachdem im folgenden Abschnitt ein paar zum Verständnis erforderliche, grundlegenden Begriffe (kurz) umrissen werden, wird im übernächsten Abschnitt erklärt, wie es zu diesem Effekt kommt. Im letzten Abschnitt wird dann diskutiert, wie man damit umgehen sollte, wenn solche Fehler unter JavaScript in Erscheinung treten.

Seitenanfang

Kleiner Exkurs: Dezimal- und Binärsystem

Um zu verstehen, was JavaScript mit Zahlen anstellt, muß man sich ein wenig mit der Darstellung von Zahlen befassen. Wer daran an dieser Stelle nicht interessiert ist oder die Begriffe Dezimalsystem, Binärsystem und Exponentialdarstellung als bekannt ansieht, kann diesen Abschnitt getrost überspringen.

Im Dezimalsystem erfassen wir einen Zahlenwert, indem wir ihn als Summe von Vielfachen der Zehnerpotenzen notieren. Obwohl sich dies vielleicht etwas kompliziert anhört, bedeutet dies nicht mehr als:

42 ist die Summe aus 4 mal 101 und 2 mal 100
Dabei ist die nullte Potenz (also 100) als 1 definiert

Dasselbe gilt auch für Teile von ganzen Zahlen. Hier benutzen wir den Dezimalpunkt (im deutschsprachigen Raum meist das Dezimalkomma), um negative Zehnerpotenzen einzubeziehen:

6.25 ist die Summe aus 6 mal 100, 2 mal 10-1 und 5 mal 10-2
Dabei ist z.B. 10-3 = 1/103 (ein Tausendstel)

Jede Stelle im Dezimalsystem kann dabei einen von zehn Zuständen ("0" bis "9") annehmen. Das ist uns geläufig, aber unpraktisch für die elektrische Datenverarbeitung. Da elektrische Signale sehr viel besser genau einen von zwei Zuständen ("Strom an" und "Strom aus") annehmen können, wählt man eine Darstellung, in der es für jede Stelle zwei mögliche Zustände (wir nennen diese "0" und "I") gibt: das Binärsystem. Hier können wir Zahlenwerte als Summe der Zweierpotenzen notieren:

II0.0I ist die Summe aus 1 mal 22, 1 mal 21 und 1 mal 2-2
Im Dezimalsystem ist das nichts anderes als 4 + 2 + 1/4 = 6.25

Da die Anzahl der benutzten Stellen einen großen Einfluß auf die Rechengeschwindigkeit haben kann, muß sie auf eine bestimmte Anzahl Stellen beschränkt werden. Um nun mit wenigen Stellen sowohl sehr große als auch sehr wenig von Null verschiedene Werte darstellen zu können, bedient man sich eines kleinen Kniffs: man schreibt den Punkt grundsätzlich hinter die erste von Null verschiedene Stelle und gibt zusätzlich an, um wieviele Stellen der Punkt dann von diesem Platz verschoben werden muß. Da das Verschieben des Punktes um z.B. drei Stellen nach rechts im Dezimalsystem einer Multiplikation mit 103 und im Binärsystem einer Multiplikation mit 23 entspricht, nennt man diese Art der Notation auch Exponentialdarstellung - wir notieren ein "E" und die Anzahl Stellen:

1.23 E+03 bedeutet 1.23 mal 103 = 1230
1.23 E-02 bedeutet 1.23 mal 10-2 = 0.0123
I.0I E+II bedeutet 1.25 mal 23 = 10
I.0I E-I0 bedeutet 1.25 mal 2-2 = 0.3125

Diese Darstellung hat neben dem o.g. Vorteil, sehr unterschiedlich große Werte mit wenigen Stellen darstellen zu können allerdings einen Nachteil: mit größer werdenden Zahlen werden auch die Abstände zwischen voneinander unterscheidbaren Werten immer größer.

Seitenanfang

Wie kommt es zu den Rechenfehlern unter JavaScript?

JavaScript erlaubt im Quelltext die dezimale, oktale und hexadezimale Angabe von Zahlenwerten. Die Ausgabe mit toString() (ohne Parameter), write() oder alert() liefert aber immer einen dezimal dargestellten Wert:

alert(42);   Test
alert(052);  Test
alert(0x2A); Test

Das legt nahe, der JavaScript-Interpreter stelle Zahlen intern dezimal dar. Tatsächlich wird aber intern zum Durchführen von Berechnungen die im vorherigen Abschnitt beschriebene Exponentialdarstellung im Binärsystem benutzt. Dazu muss jede Eingabe in dieses Format und jede Ausgabe ins Dezimalsystem umgerechnet werden. Dies bedingt aber ein großes Problem: Nicht jede Zahl, die sich in einem Zahlensystem mit wenigen Stellen darstellen läßt, hat diese Eigenschaft auch in allen anderen Zahlensystemen.

Gravierend wird das schon bei Zahlen mit "ein paar" dezimalen Nachkommastellen: diese lassen sich in der Regel im Binärsystem überhaupt nicht mit einer endlichen Anzahl Stellen genau wiedergeben! Hier nur zwei Beispiele für Zahlen, die dezimal lediglich eine Nachkommastelle besitzen, binär hingegen unendlich periodisch sind (der Strich über einer Ziffernreihe bedeutet hierbei, daß diese unendlich oft wiederholt werden müßte, um den Wert exakt darzustellen).

                         ____
  dezimal 0.1  - binär I.I00I E-I0I
                         ____________________
  dezimal 0.01 - binär I.0I000IIII0I0III0000I E-I000

Daß solche unendlichen Wiederholungen gerundet werden, um sie mit der zur Verfügung stehenden, endlichen Anzahl Stellen darzustellen, hat zur Folge, daß viele Zahlen, die wir als "relativ glatt" ansehen, für Berechnungen nur näherungsweise (also mit einem kleinen Fehler) zur Verfügung stehen. Betrachten wir noch einmal zwei der Beispiele aus der Einleitung:

alert(0.3 + 0.1);   Test
alert(0.1 + 0.2);   Test

Nun können wir nachvollziehen, was passiert: Jeder Summand wird für sich vor der Addition ins Binärsystem umgewandelt. Dabei tritt jeweils der erwähnte kleine Fehler auf. Beide kleine Fehler addieren wir nun mit ins Ergebnis. Haben diese Fehler (wie im zweiten Beispiel) dasselbe Vorzeichen, ist ihre Summe dem Betrage nach größer, als jeder für sich - so groß, daß sie sich nach der Umwandlung des Ergebnisses ins Dezimalsystem bemerkbar macht. Haben diese Fehler (wie im ersten Beispiel) unterschiedliche Vorzeichen, heben sie sich in der Summe dagegen ganz oder teilweise auf.

Seitenanfang

Ungenaue Ergebnisse immer runden?

Wie gravierend sind die beschriebenen Fehler für das Rechnen unter JavaScript? Die so zu erzielenden Fehler fallen sehr ins Auge - wir erwarten eine Zahl mit einer und erhalten eine Zahl mit etlichen Nachkommastellen. Trotzdem sind diese Fehler sehr klein. Betrachten wir nochmals eines der Beispiele:

alert(0.1 + 0.2);   Test

Der Fehler beträgt hier rund 5 mal 10-17. Gemessen am erwarteten Ergebnis von 0.3 bewegt er sich damit im Bereich von ein paar billiardstel Prozent. Das bedeutet, um mit solchen Werten weiterzurechnen, sind sie in der Regel wohl doch hinreichend genau (wenn sie auch nicht besonders ästhetisch wirken). Auf das Runden von Zwischenergebnissen sollte man daher ruhig verzichten.

Um solche Werte allerdings als Ergebnis anzuzeigen, sind sie sehr viel weniger gut geeignet. Wollen wir beispielsweise in einem Formular Warenwerte addieren und das Ergebnis in einem Formularfeld ausgeben, müssen wir den Fehler korrigieren. Das Runden von Endergebnissen auf eine sinnvolle Anzahl Stellen für die Ausgabe wird deshalb auf der Seite "Zahlen runden und ausgeben" genau beschrieben.

top

Diese Seite ist Teil der de.comp.lang.javascript FAQ. Die Einstiegsadresse lautet http://www.dcljs.de/. Der Text der Seite wurde erstellt von Dietmar H. G. Meier (©).

 ______ letzte Änderung: 12/2012 ______ 

 
© S. Mintert, Ch. Kühnel