Fehler in der Funktion »Obergrenze«

Von Jürgen Schell. | 13 Januar, 2012 - 12:44
Frage:

Wenn ich in RagTime die Obergrenze von 2,7/0,15 ausrechne, kommt 19 heraus. Es muss aber 18 sein. Weshalb rechnet RagTime hier falsch? (Die Formel im Rechenblatt: »Obergrenze(2,7/0,15)«)

Antwort:

Leider ist die Antwort sehr technisch.

Dies ist in eines der vertrackten Probleme von numerischem Rechnen auf Computern. Leider ist dieses Problem wenig bekannt. Die Annahme ist immer: »Rechnen können Computer schon richtig«. Leider können sie es bei Zahlen mit Nachkommastellen oft gar nicht! Das Problem ist nicht prinzipiell lösbar. ich versuche das mal am Beispiel »Obergrenze(2,7/0,15)« zu erklären. Die Formel liefert 19, »2,7/0,15« dagegen 18.

Nur zum Spaß hab ich dasselbe mal in AppleScript und PHP probiert. AppleScript: »round (2.7 / 0.15) rounding up« liefert 19, »2.7 / 0.15« dagegen 18.0. PHP: »ceil(2.7 / 0.15))« liefert 19, »2.7 / 0.15« liefert 18. Dies nur als Illustration, dass RagTime da in guter Gesellschaft ist.

Was da passiert:

Zuerst ein Beispiel im Dezimalsystem. 1/3 ergibt 0,3333333333… Die Zahl ist periodisch. Rechnet man mit begrenzt viele Stellen, wird irgendwo abgeschnitten. Die Zahl ist dann nicht mehr genau. Nehmen wir als Beispiel 5 Nachkommastellen. »(1/3)*3« mit fünf Nachkommastellen gerechnet ergibt 0,99999 und nicht 1.

Sehr viele Zahlen, die im Dezimalsystem ganz einfach aussehen (0,1 zum Beispiel), sind dual periodisch. Das bedeutet: Der Rechner kann sie intern nicht exakt darstellen. Man kann auch sagen: Die Zahl 0,1 gibt es auf einem Rechner schlicht nicht. Es gibt nur einen Wert dicht dran.

Bei einer Zahl mit 64 bit (wohl am häufigsten benutzt) wird 2,7 im Hauptspeicher zu 2,7000000000000002. Aus der Zahl 0,15 wird intern 0,14999999999999999. Und mit den Zahlen wird dann gerechnet. Das Ergebnis von »2,7 / 0,15« ist intern im Rechner »18.000000000000004«. Das dürfte das Ergebnis auf jedem Rechner sein, der ISO-konform mit einer 64-bit-Zahl rechnet.

Rechenblätter kaschieren das Problem bisweilen, und dann auch durch runden. In AppleScript liefert «(0.1 + 0.1 + 0.1 - 0.3) = 0» das Ergebnis »false« (der entsprechende Ausdruck in PHP, »(0.1 + 0.1 + 0.1 - 0.3) == 0«, ebenfalls). In RagTime liefert der entsprechende Ausdruck im Rechenblatt 1 (also true oder wahr). Intern sind die Ausdrücke tatsächlich nicht gleich. Der Klammerausdruck liefert «5,5511151231257827 * 1017». Das korrekte Ergebnis in RagTime entsteht nur, weil wir dort absichtlich ungenauer rechnen (d.h. runden). Diese Tricks werden aber immer nur einen Teil der Fälle abfangen. (Verschiedene Tabellenkalkulationsprogramme verwenden leicht unterschiedliche Tricks, deshalb sind die Beispiele, in denen so ein Problem offensichtlich wird, auch verschieden.)

Wenn man die maximale Anzahl der möglichen Nachkommastellen kennt, kann man sich mit Runden helfen: »Obergrenze(Runden(2,7/0,15;5))« liefert 18. Hier wird angenommen, dass die Division maximal fünf Nachkommastellen liefern kann und entsprechend gerundet. Die Funktion »merkt« dann, dass das Ergebnis eine ganze Zahl ist und die haben das Problem nicht.

Dieser Text ist sehr technisch, aber es handelt sich wirklich um ein Grundsatzproblem des numerischen Rechnens mit Fließkommazahlen auf Computern. Eigentlich sind diese Zahlen mit dem Konzept exakter Schwellwerte (wie sie beim Auf- und Abrunden nun einmal vorkommen) nicht verträglich. Bei Zahlen mit Nachkommastellen kann man sich auf bestimmte Berechnungen am Computer nicht verlassen!

Vorsicht auch bei nur unwesentlichen Rundungen

#10417 On 14 Januar, 2012 21:49 Andreas Loos said,

Danke, Jürgen, dass Du auf dieses Problem hinweist.
Vielleicht werden einige Nutzer jetzt skeptischer gegenüber dem "allmächtigen" Computer, was dringend nötig ist.
Selbst wenn es sich um Zahlen handelt, die im Dualsystem mit endlich vielen Stellen dargestellt werden, kann es zu dem beschriebenen Fehler kommen: "Obergrenze(A3)" kann auch zu "5" führen, wenn in A3 sichtlich eine "4" steht. Nämlich dann, wenn die "4" nicht als Ganzzahl eingegeben wurde sondern als berechnetes Ergebnis erscheint. Dann kann es in den Tiefen des numerischen Systems von RagTime nämlich irgendeine von Null verschiedene Nachkommastelle geben, die die Funktion "Obergrenze" verfälscht.
Zur Schärfung der Vorsicht möchte ich noch ein Beispiel anfügen.
Das Gleichungssystem

5,61x – 5,1y = 510
6,0006x – 6,0y = 0,6

hat die Lösung x = 1000, y = 1000.
Rundet man nun nur "wenig" auf eine Stelle nach dem Komma und arbeitet mit dem System

5,6x – 5,1y = 510
6,0x – 6,0y = 0,6

so erhält man x = 13,6..., y = 43,4... Das ist immerhin eine bemerkenswerte Abweichung des Ergebnisses bei einer Abweichung des Systems von nur höchstens 0,17%.

Noch eine Frage: in Deinen Ausführungen verstehe ich nicht den Satz: "Der Klammerausdruck liefert «5,5511151231257827 * 1017»" Welcher Klammerausdruck soll das sein? Oder meinst Du «10–17»?

Gruß
Andreas Loos

Klammerausdruck

#10418 On 15 Januar, 2012 13:06 Jürgen Schell said,

Ich meinte »(0.1 + 0.1 + 0.1 - 0.3)« mit dem Klammerausdruck.

In AppleScript kann man Werte über »System Events« als Property Lists serialisieren. Damit kann man ganz gut prüfen, was AppleScript da eigentlich für genau hält. Im Fall von (0.1 + 0.1 + 0.1 - 0.3) kommt dabei 5.5511151231257827e-17 heraus und nicht 0.

Vielleicht noch ein Hinweis: Auch Auffüllen kann zu solchen Effekten führen. In dem Dokument, dass zu dem Artikel geführt hat, gab es folgende Situation:

In Zelle A1 ist 0,1 getippt worden, in A2 der Wert 0,2. Dann ist nach unten aufgefüllt wurden.

Dabei wird natürlich von Zeile zu nächster Zeile je 0,1 addiert. Und diese Zahlen sind dann nicht ganz exakt.

In A6 steht nach dem Auffüllen der Wert 0,6. Obergrenze(A6;0,1) liefert 0,7!

(Ich hab übrigens mal mit Excel verglichen: Die runden das Problem weg, was RagTime an anderen Stellen auch tut. Mag sein, dass wir bei Obergrenze das Argument auch etwas runden sollten. Dann treten diese Effekte bei Zahlen mit wenigen Nachkommastellen kaum noch auf. Aber 100% zuverlässig wird das auch nie.)

Jürgen

www.j-schell.de