Rechnen

Sie haben nun einen Überblick darüber, wie Forth arbeitet. Ab hier soll es mehr in die Details gehen und ihnen die Feinheiten von Forth näher gebracht werden. In diesem Sinne ist dieses und die folgenden Kapitel eher als Nachschlagewerk, denn als Tutorium zu verstehen. In der Reihenfolge der Themen werde ich mich an dem wunderbaren Buch 'Starting Forth' von Leo Brodie orientieren, auch wenn es einige -- zeitbedingte -- Anpassungen geben wird. Leo Brodie beschreibt zum Beispiel noch ein Forth-System, das Betriebssystem, Kompiler und Interpreter war, das ist heute nicht mehr so gegeben und daher dann die Anpassungen.

In diesem Kapitel geht es ums Rechnen, also um die ureigenste Domäne von Computern. In Anlehnung an Brodie werde ich hier nur die Möglichkeiten für den Ganzzahlbetrieb von Forth vorstellen. Moderne Forth-Umgebungen haben zwar meist auch die Möglichkeit mit Fließkommazahlen zu arbeiten, aber das Grundprinzip lässt sich einfacher (und wie ich finde auch besser) mit Ganzzahlen darstellen.

Bevor es losgeht soll aber noch ein Wort zu Kommentaren verloren werden. Ein Kommentar wird in Forth durch das Wort ( eingeleitet. Alles was danach bis zu einer schliessenden Klammer folgt wird vom Interpreter ignoriert. Da es sich um ein Wort im Forthsinne handelt muss der Klammer ein Leerzeichen folgen. Weiterhin gibt es auch noch die Möglichkeit einen Zeilenkommentar durch einen Backslash, also \ zu beginnen. Nach diesem Wort wird alles bis zum Ende der Zeile als Kommentar verstanden.

Es hat sich eingebürgert die Zeilenkommentare mit \ für den eigentlichen Kommentar zu verwenden, während die Klammer für die Kommentierung von Stackeffekten zu verwenden. Was ist damit gemeint?

Schon im letzten Kapitel haben sie gesehen, dass es beim Programmieren in Forth sehr wichtig ist, den Stack im Auge zu behalten. Daher wird bei den meisten Wörtern in Forth mit angegeben, wie sie den Stack verändern und das eben in einem Klammerkommentar. So bedeutet zum Beispiel:

  + ( n1 n2 -- n )

dass die Addition (die durch das Pluszeichen, genauer das Pluswort durchgeführt wird) zwei Zahlen auf dem Stack erwartet, das bedeutet das 'n1 n2' vor dem '--' und eine Zahl hinterlässt; das 'n' hinter dem '--'. Generell wird dieser Stackkommentar so aufgebaut:

( vorher -- nachher )

Die Reihenfolge der Angaben auf dem Stack bezeichnen dabei, wo auf dem Stack die Werte liegen. Links liegt der tiefste und rechts immer der oberste Wert auf dem Stack. Im weiteren werde ich diese Notation beibehalten und sie sollten sich ebenfalls angewöhnen sie zu verwenden; es ist dann einfach leichter den Überblick zu behalten.

Aber nun zum Rechnen. Das einfachste Wort ist natürlich

+ ( n1 n2 -- summe )

und er macht genau das, was man von ihm erwartet: Er addiert! Und wie er das macht, ergibt sich aus dem Stackkommentar. Die beiden obersten Zahlen auf dem Stack werden addiert und das Ergebnis wird auf dem Stack abgelegt. Das Wort ist, wie die meisten in Forth, 'fressend'. Das heißt, dass die beiden obersten Zahlen auf dem Stack nach dieser Addition weg sind.

Auch die Wörter

* ( n1 n2 -- produkt )

- ( n1 n2 -- differenz )

arbeiten wie zu erwarten. Der erstere multipliziert die beiden obersten Zahlen auf dem Stack und ersetzt sie durch ihr Produkt. Beim zweiten wird die oberste Zahl auf dem Stack von der zweitobersten subtrahiert und die Differenz der beiden auf dem Stack abgelegt. Hierbei sollte man sich klar machen, dass das Minuszeichen von der (uns gewohnten) Position zwischen den Zahlen einfach hinter die Zahlen gewandert ist. Man gibt also wie gewohnt zuerst den Minuenden und dann den Subtrahenden an; nur das Minuszeichen steht an 'ungewohnter' Stelle.

Ein bisschen mehr Augenmerk sollte man auf das Wort

/ ( n1 n2 -- quotient )

richten. Der Grund dafür ist, dass es sich um eine ganzzahlige Division handelt. Sie entspricht damit dem, was normalerweise in der Grundschule gerechnet wird, ohne allerdings den 'Rest' anzugeben. War in der Grundschule zum Beispiel: '7 durch 3 gleich 2 Rest 1', dann gilt für /:

  7 3 /
  ok
  .
  2 ok

Untersucht man danach z.B. mit .S den Stack, dann zeigt sich, dass / tatsächlich keinen Rest auf dem Stack hinterlegt hat. Wie wir im letzten Kapitel gesehen haben, gibt es dafür das Wort /MOD. Was die Sache aber interessant macht, ist, dass es im Gegensatz zur Grundschule in Forth auch negative Zahlen gibt.

Bei den positiven Zahlen ist die Sache klar. Die Ganzzahldivision ergibt immer das Ergebnis, das kleiner oder gleich dem Quotienten der beiden Zahlen ist. So ergibt: '7 durch 3' ja den Wert sieben Drittel, oder als Dezimalzahl: 2 Komma Periode-3. Als Ergebnis der Ganzzahldivision wird die Zahl genommen, die dann kleiner ist, also die 2. Nur wenn die Division 'aufgeht' wird die Zahl genommen, die gleich dem Quotienten ist.

Wie sieht es nun bei negativen Zahlen aus? Folgte man der obigen Logik, dann wäre '-7 durch 3 gleich -3', denn der Quotient hat den (dezimalen) Wert: '-2 Komma Periode-3' und die nächst kleinere ganze Zahl ist dann die '-3'.

So arbeitet Forth aber nicht. Statt dessen wird in Forth so dividiert, wie bei den positiven Zahlen und dann ein Minuszeichen davor gesetzt. In Forth ist also:

  -7 3 / .
  -2 ok

und auch

  7 -3 / .
  -2 ok

Es gibt noch zwei Worte, die ich ihnen besonders ans Herz legen will.

Seien wir doch mal ehrlich! Ich meine richtig ehrlich! Wir alle waren mal im Mathematikunterricht in der Schule und haben dort jede Menge gelernt, was wir nie wieder gebraucht haben.

Das Einzige, was wir im Mathematikunterricht gelernt haben und immer noch und immer wieder brauchen, ist der Dreisatz. Betrachten wir den doch mal näher.

Eine Ein-Liter-Flasche Cola kostet (incl. MWSt 239 Cent). Nun könnte es interessieren, wieviel hier der Steueranteil ist. In einer 'klassischen' Dreisatzrechnung sähe das folgendermaßen aus:

Statt die Rechnung nun so aufzuschreiben, wie es im obigen Bild dargestellt ist, kann man auch vereinfacht sagen, dass der Kaufpreis mit dem Bruch 19-119tel multipliziert werden muss. Man nennt eine solche Rechnung, bei der ein Wert erst multipliziert und dann sofort wieder dividiert wird, auch Skalierung. Und genau dafür bietet Forth ein eigenes Wort an:

*/ ( n1 n2 n3 -- n4 )

Dabei ist 'n4' das Ergebnis der Rechnung: 'n1*n2/n3', also genau der Rechnung, die beim Dreisatz so wichtig ist, der Skalierung. Wendet man diesen Operator auf das obige Problem an, dann erhält man:

  239 19 119 */  ok
  . 38  ok

Ein eigens bemühter Taschenrechner würde uns verraten, dass der Steueranteil bei 38.15966386554622 Cent liegt -- mir ist das Forth-Ergebnis lieber, wer braucht schon die ganzen Nachkommastellen.

Man könnte nun einwenden, dass man die Nachkommastellen braucht, um im Zweifelsfall richtig zu runden. Aber auch das bekommt man ohne Nachkommastellen hin. Betrachten wir ein Beispiel, mit dem eine Prozentangabe berechnet werden kann; Eingeweihte wissen, dass es sich dabei nur um eine Sonderform des Dreisatz handelt.

Ein einfacher Ansatz wäre:

  : % 100 */ ;

Das funktioniert in einigen Fällen:

  239 19 % . 45  ok
  1234 19 % . 234  ok

Vergleicht man die Werte von Forth mit Werten eines Taschenrechners, dan sieht man, dass die Werte von Forth 'richtig' sind, Anders sieht das allerdings aus, wenn man rechnet:

  239 35 % . 83  ok

Der Taschenrechner liefert hier den Wert: 83.65, der nach den üblichen Rundungsregeln zu 84 gerundet werden muss. Wie bekommt man das in Forth hin? Nun, eigentlichganze einfach:

  : % 10 */ 5 + 10 / ;
  239 35 % . 84  ok

Der Wert wurde zunächst nur durch 10 geteilt, so dass die zu rundende Nachkommastelle noch vod dem Komma lag. Wenn man dann 5 addiert und dann erneut durch 10 dividiert, erhält man immer das richtige Ergebnis.

Ergänzend zu dem Divisionswort stellt Forth noch zwei weitere Wörter zur Verfügung, die im Zusammenhang mit der Division wichtig sind:

/MOD ( u1 u2 -- u-rest u-quotient )

den sie ja schon kennen und das Wort der die direkte Ergänzung zu / ist:

MOD ( u1 u2 -- u-rest )

Bei diesem Wort wird nun der Divisionsrest der Division u1 durch u2 zurück gegeben. Auffällig ist, dass im Stackkommentar nicht n1 n2 ... steht, sondern u1 u2... Damit ist gemeint, dass die Argumente vorzeichenlos interpretiert werden. Den Unterschied zwischen vorzeichenbehafteten und vorzeichenlosen Zahlen werde ich noch erläutern. Hier reicht es zu wissen, dass vorzeichenlose Zahlen immer positiv oder Null sein; negative Zahlen kommen nicht vor.

Es gibt moderne Forth-Systeme, welche die beiden Wörter MOD und /MOD auch mit Vorzeichen anbieten und auch erklären, wie sie die Division verstehen (z.B. ob es negative Reste geben kann). Ich würde allerdings davon abraten diese Wörter mit negativen Zahlen zu verwenden.

Passend zum Skalierungswort */ gibt es natürlich auch dazu einen passendes Wort:

*/MOD ( n1 n2 n3 -- n4 n5 )

Hier wird nicht nur der skalierte Wert sondern auch der Divisionsrest zurück gegeben.

Stackmanipulationen

Die wichtigsten Wörter zur Manipulation des Stacks kennen sie ja schon. Dennoch sollen sie hier kurz wiederholt werden. Das erste und einfachste Stackwort ist:

DROP ( n -- )

Dieses Wort macht nichts anderes als den obersten Wert vom Stack zu nehmen und ins Nirvana zu schicken (wenn es ihnen lieber ist: nach /dev/null); mit einem Wort zu löschen. Dieses Wort wird oft verwendet, um den Stack 'aufgeräumt' zu hinterlassen. Das nächste, auch noch sehr einfache Wort ist:

DUP ( n -- n n )

Hierbei handelt es sich einfach darum, dass dieses Wort den obersten Eintrag auf dem Stack verdoppelt. Dies ist besonders dann nützlich, wenn man einen Wert für eine Zwischenrechnung oder einen Test braucht, danach aber noch damit weiter arbeiten möchte. Als nächstes möchte ich an das Wort

SWAP ( n1 n2 -- n2 n1 )

erinnern. Wie der Stackkommentar zeit, dient das Wort dazu die beiden obersten Werte auf dem Stack zu tauschen. Weiterhin kennen sie

OVER ( n1 n2 -- n1 n2 n1 )

und

ROT ( n1 n2 n3 -- n2 n3 n1 )

Ersterer kopiert den zweitobersten Wert auf dem Stack nach oben während ROT keine Werte hinzufügt, sondern den Stack 'rotiert' indem der drittoberste nach oben geholt wird.

Ich gebe zu, dass ich zu den Menschen gehöre, die bei Tutorien die Beispielaufgaben nie oder fast nie löst, weil ich eine Sprache lieber an einem konkreten Problem ausprobiere, auch wenn das dazu führen kann, dass ich große Teile von Code neu schreibe, wenn ich die Sprache näher kenne. Trotz- oder vielleicht gerade wegendem möchte ich sie auffordern die am Ende der Seite stehenden Aufgaben zur Stackmanipulation doch zu bearbeiten und seien sie dabei ehrlich sich selbst gegenüber. Forth programmieren ohne Kenntnisse dessen, was sich auf dem Stack tut, geht einfach nicht und da braucht es Übung.

In einem späteren Kapitel werden wir sehen, dass Forth neben den einfachen Ganzzahlen auch noch 'doppelte' Zahlen kennt. Sie brauchen doppelt so viel Speicherplatz, daher der Name. Auch für diese Zahlen gibt es Worte, mit denen man den Stack manipulieren kann. Im Stackkommentar erscheinen doppelte Zahlen als 'd'. Um den Unterschied zwischen den 'einfachen' und den 'doppelten' Worten erklären zu können, soll das Wort

2DUP ( d -- d d )

genommen werden. Der Unterschied zu, zum Beispiel DUP DUP wird deutlich, wenn man sich den Stack ansieht. Dabei soll der Stack mit 'einfachen' Zahlen gefüttert werden:

  DUP DUP ( n1 n2 -- n1 n2 n2 n2 )
  2DUP    ( n1 n2 -- n1 n2 n1 n2 )

Man sieht den Unterschied deutlich. die '2'-Worte nehmen einfach zwei einfache Zahlen als eine Zahl.

Analog arbeiten die folgenden Wörter und man sollte sich klar machen, dass man sie auch mit 'einfachen' Zahlen sehr gut nutzen kann. Ein 'doppeltes' Wort, nämlich 2@ haben wir ja schon im vorigen Kapitel verwendet, obwohl wir damit zwei 'einfache' Zahlen bearbeitet haben.

2DROP ( d -- )

2OVER ( d1 d2 -- d1 d2 d1 )

2SWAP ( d1 d2 -- d2 d1 )

Ein paar nützliche Worte

Neben den bisher behandelten Worten, mit denen man in Forth rechnen kann, gibt es noch ein paar weitere, die ich hier kurz vorstellen möchte. Viele davon kann man sich selber schnell 'bauen', einige davon sind allerdings noch nicht bekannt und ziemlich wichtig.

Die erste Gruppe von Wörtern ist eigentlich selbsterklärend. Sie können leicht durch eigene Definitionen 'gebaut' werden, aber sie sind in der Regel schneller als eine eigene Definition.

1+ ( n -- n + 1 )

Addiert einfach eine Eins zur obersten Zahl auf dem Stack. Analog arbeitet das folgende Wort, dass eine Eins von der Zahl auf dem Stack subtrahiert.

1- ( n -- n - 1 )

Das Gleiche gibt es dann auch noch für die '2':

2+ ( n -- n + 2 )
2- ( n -- n - 2 )

Ziemlich interessant sind die beiden folgenden Wörter. Was sie tun ist wieder nahezu selbsterklärend, aber sie arbeiten nach einem bestimmten Prinzip, was sie ausgesprochen wertvoll machen kann. Das eine Wort multipliziert eine Zahl mit 2, das andere dividiert sie durch zwei. Intern dürfte bei den meisten Implementationen ein Shift des Arguments sein (kommt noch).

2* ( n -- Linksshift um ein Bit)
2/ ( n -- Rechtsshift um ein Bit)

Auch bei den folgenden Wörtern erklärt sich ihre Arbeitsweise eigentlich durch ihren Namen.

ABS ( n -- Absolutwert von n )
NEGATE ( n -- -n )
MIN ( n1 n2 -- Minimum der beiden Zahlen )
MAX ( n1 n2 -- Maximum der beiden Zahlen )

Die nun folgende Gruppe von Wörtern ist nun nicht mehr so einfach zu verstehen. Zuvor muss noch etwas geklärt werden, was zwar schon angesprochen wurde, aber bisher noch nicht so intensiv von Bedeutung war. Der Stack, von dem bisher die Rede war, ist nicht der einzige Stack, über den Forth verfügt. Es gitb mindestens noch einen, den Returnstack, dessen Bedeutung in einem späteren Kapitel noch sehr eingehend betrachtet werden wird, weil er für die 'Abarbeitung' von Forth-Programmen sehr wichtig ist. Für diesen Returnstack gibt es einige Wörter, die außerordentlich wichtig werden können.

>R ( n -- )

Dieses Wort verschiebt den obersten Wert vom Zahlenstack auf den Returnstack. Dieses Wort kan hervorragend verwendet werden, um Zwischenergebnisse irgendwo aufzubewahren, die man erst später in einer Rechnung noch mal braucht. Allerdings hier schon mal die Warnung: Wird innerhalb einer Doppelpunktdefinition dieses Wort verwendet, dann muss auch das Wort

R> ( -- n )

verwendet werden, denn sonst kommt es mit fast tödlicher Sicherheit zu einem Systemabsturz. Das letztere Wort holt eine Zahl vom Returnstack wieder zurück auf den Zahlenstack. Der Returnstack dient zur Ablaufkontrolle und daher würde ein falscher Wert dort zu, eben angesprochenen Systemabsturz führen.

Neben diesen beiden wichtigen Worten sollen noch drei weitere vorgestellt werden, die allerdings vor allem im Zusammenhang mit Schleifen eine Rolle spielen werden:

I ( -- n)
I' ( -- n)
J ( -- n)

Diese drei Wörter kopieren in der Reihenfolge ihrer Nennung den ersten, zweiten oder dritten Wert auf dem Returnstack auf den Zahlenstack. Das ist vor allem dann wichtig, wenn man auf Zwischenergebnisse oder Werte mehrfach zugreifen will.

Will man zum Beispiel den Wert von ax^2+bx+c für einen bestimmten x-Wert berechnen, dann formt man den Term am einfachsten erst mal zu a(ax+b)+c um und kann dann folgendermaßen vorgehen:

  : QUADRAT ( a b c x -- wert )
    R>                    \ abc       | x
    SWAP ROT              \ cba       | x
    I                     \ cbax      | x
    *                     \ cb ax     | x
    +                     \ c ax+b    | x
    R> *                  \ c x(ax+b) |
    + ;                   \ x(ax+b)+c |

Aufgaben

  1. Welches Ergebnis ergibt der Ausdruck:
          2 3 + 5 *
        
    und welches Ergebnis bringt:
          5 2 3 + *
        
    [Lösung]
  2. Bringen sie die folgenden vier mathematischen Ausdrücke in die Forth-Schreibweise. Achtung, es gibt mehrere Möglichkeiten. [Lösungen]A
  3. Schreiben sie ein Wort, das die obersten drei Zahlen auf dem Stack 'swapt', also aus "a b c" "c b a" macht. [Lösung]
  4. OVER, ohne OVER zu verwenden. [Lösung]
  5. In manchen Forth-Varianten gibt es das Wort -ROT, der wie ROT arbeitet, aber 'umgekehrt' rotiert. Aus "a b c" wird "c a b". Schreiben sie das Wort. [Lösung]
  6. Schreiben sie ein Wort, das den Ausdruck "(n + 1) / n" berechnet und dabei den Stackeffekt: "( n -- Ergebnis )" hat. [Lösung]
  7. Schreiben sie ein Wort, das den Ausdruck "x ( 7 x + 5 )" berechnet und dabei den Stackeffekt: "( x -- Ergebnis )" hat. [Lösung]
  8. Wie könnte die Definition eines Wortes aussehen, das die vier obersten Stackeinträge in eine umgekehrte Reihenfolge bringt, also aus " a b c d --- d c b a " macht? [Lösung]
  9. 3DUP mit dem Effekt: ( a b c -- a b c a b c ) [Lösung]