Zeitreihenanalyse und ihre Anwendungen: Mit R Beispiele Code, der in der Third Edition verwendet wird Beispiele Im Folgenden ist der Code für jedes numerische Beispiel im Text verwendet. Dieses Zeug funktioniert nicht, es sei denn, Sie haben astsa und die Datendateien zu Beginn der Sitzung geladen. Wenn dies Ihr erstes Mal hier ist, möchten Sie vielleicht die astsa Paket Notizen Seite für weitere Informationen zu lesen. Kapitel 1 Beispiel 1.1 Beispiel 1.2 Beispiel 1.3 Beispiel 1.4 Beispiel 1.5 Beispiel 1.6 Beispiel 1.7 Beispiel 1.9 Beispiel 1.10 Beispiel 1.11 Beispiel 1.12 Beispiel 1.24 Beispiel 1.25 Beispiel 1.26 Beispiel 1.27 Kapitel 2 Beispiel 2.1 Beispiel 2.2 Beispiele 2.3 Beispiele 2.4 und 2.5 Beispiel 2.6 Beispiel 2.7 Beispiel 2.8 Beispiel 2,9 Beispiel 2.10 Beispiel 2.13 Beispiel 2.12 Beispiel 2.13 Beispiel 2.14 Beispiel 2.15 Kapitel 3 Beispiel 3.1 Beispiel 3.4 Beispiel 3.10 Beispiel 3.11 Beispiel 3.15 Beispiel 3.17 Beispiel 3.24 Beispiel 3.27 Beispiel 3.28 Beispiel 3.29 Beispiel 3.35 Beispiel 3.37 Beispiel 3.38 Beispiel 3.40 Beispiel 3.44 Beispiel 3.46 Kapitel 4 Beispiel 4 Beispiel 4.10 Beispiel 4.11 Beispiel 4.12 Beispiel 4.13 Beispiel 4.14 Beispiel 4.15 Beispiel 4.18 Beispiel 4.19 Beispiel 4.21 Beispiel 4.22 Beispiel 4.23 Beispiel 4.24 Beispiel 4.25 Beispiel 4.26 Kapitel 5 Beispiel 5.1 Beispiel 5.2 Beispiel 5.3 Beispiel 5.4 Beispiel 5.5 Beispiel 5.6 Beispiel 5.9 Beispiel 5.10 Beispiel 5.9 Beispiel 5.10 Beispiel 5.11 Beispiel 6.2 Beispiel 6.2 Beispiel 6.5 Beispiel 6.6 Beispiel 6.7 Beispiel 6.8 Beispiel 6.9 Beispiel 6.10 Beispiel 6.12 Beispiel 6.13 Beispiel 6.17 Beispiel 6.18 Beispiel 6.19 Beispiel 6.23 Kapitel 7 Code in Einleitung Beispiel 7.1 Beispiel 7.2 Beispiel 7,7 Beispiel 7,7 Beispiel 7,7 Beispiel 7,8 Beispiel 7,9 Beispiel 7,10 Beispiel 7,11 Beispiel 7.12 Beispiel 7.13 Beispiel 7.14 Beispiel 7.15 Beispiel 7.16 Beispiel 7.18 Beispiel 7.19 Beispiel 7.20MOVING FORTH Teil 1: Designentscheidungen im Forth Kernel von Brad Rodriguez Dieser Artikel erschien zuerst im Computer Zeitschrift 59 (JanuarFebruar 1993). EINFÜHRUNG Jeder in der Forth-Community spricht darüber, wie einfach es ist, Port zu einer neuen CPU zu portieren. Aber wie viele quoteasyquot und quotobviousquot Aufgaben, ist nicht viel geschrieben, wie es zu tun ist So, als Bill Kibler dieses Thema für einen Artikel vorschlug, beschloss ich, mit der großen mündlichen Tradition von Forthwrights zu brechen und den Prozess in Schwarz und Weiß zu dokumentieren. Im Laufe dieser Artikel werde ich Forths für die 6809, 8051 und Z80 entwickeln. Ich mache das 6809, um ein einfaches und konventionelles Forth-Modell zu veranschaulichen, und Ive veröffentlichte bereits einen 6809 Assembler ROD91, ROD92 und Ill, der einen 6809 Forth für zukünftige TCJ-Projekte benötigt. Ich mache das 8051 Forth für ein Universitätsprojekt, aber es illustriert auch einige ziemlich unterschiedliche Designentscheidungen. Die Z80 Forth ist für alle CPM Leser von TCJ, und für einige Freunde mit TRS-80s sammeln Staub. DIE WESENTLICHE HARDWARE Sie müssen eine CPU wählen. Ich werde nicht in die Vorzüge einer CPU über eine andere für Forth, da eine CPU-Wahl ist in der Regel auf Sie durch andere Überlegungen gezwungen. Außerdem ist der Gegenstand dieses Artikels zu zeigen, wie man Forth zu jeder CPU verschieben kann. Sie können den üblichen 16-Bit-Forth-Kernel (siehe unten) erwarten, um etwa 8 KByte des Programmraums zu besetzen. Für einen vollständigen Kernel, der Forth Definitionen kompilieren kann, sollten Sie mindestens 1 KByte RAM speichern. Um das Block-Management-System von Forths für den Festplattenspeicher zu verwenden, sollten Sie 3 KByte oder mehr für Puffer hinzufügen. Für ein 32-Bit-Forth-Modell, doppelte diese Zahlen. Dies sind die Minima, um einen Forth Kernel zu bekommen und zu laufen. Um eine Anwendung auf Ihrer Hardware auszuführen, sollten Sie PROM - und RAM-Größen anpassen. 16 OR 32 BIT Die von Forth verwendete Wortgröße ist nicht unbedingt dieselbe wie die der CPU. Das kleinste praktische Forth ist ein 16-Bit-Modell, d. h. eins, das 16-Bit-Integer und 16-Bit-Adressen verwendet. Die Forth-Community nennt dies die quotcellquot-Größe, da quotwordquot auf eine Forth-Definition verweist. 8-Bit-CPUs unterstützen fast immer 16-Bit-Forths. Dies erfordert in der Regel eine explizite Codierung von Double-Byte-Arithmetik, obwohl einige 8-Bit-CPUs einige 16-Bit-Operationen haben. 16-Bit-CPUs laufen häufig 16-Bit-Forths, obwohl die gleichen Doppelpräzisionstechniken verwendet werden können, um ein 32-Bit-Forth auf einer 16-Bit-CPU zu schreiben. Mindestens ein 32-Bit-Forth wurde für den 80868088 geschrieben. 32-Bit-CPUs laufen normalerweise 32-Bit-Forths. Ein kleineres Forth-Modell spart selten Code-Länge oder Prozessor-Zeit. Allerdings kenne ich mindestens einen 16-Bit-Forth, der für die 68000 geschrieben wurde. Dies schrumpft die Anwendungscodegröße um einen Faktor von zwei, da High-Level-Forth-Definitionen eine Zeichenfolge von 16-Bit-Adressen werden, anstatt eine Zeichenfolge von 32- Bit-Adressen. (Das wird in Kürze deutlich.) Die meisten 68000s haben zwar viel RAM. Alle in diesem Artikel beschriebenen Beispiele sind 16-Bit-Forths, die auf 8-Bit-CPUs laufen. DAS GEWINDESTECHNIK quotDhreaded codequot ist das Markenzeichen von Forth. A Forth quotthreadquot ist nur eine Liste von Adressen von Routinen, die ausgeführt werden sollen. Sie können dies als eine Liste der Unterprogramme anrufen, mit der CALL-Anweisungen entfernt. Im Laufe der Jahre wurden viele Threading-Variationen entworfen, und welche am besten hängt von der CPU und der Anwendung ab. Um eine Entscheidung zu treffen, musst du verstehen, wie sie arbeiten und ihre Kompromisse. Indirektes Threaded Code (ITC) Dies ist die klassische Forth-Threading-Technik, die in Abb. Forth und F83 verwendet wird und in den meisten Büchern auf Forth beschrieben ist. Alle anderen Threading-Schemata sind quotimprovementsquot auf diesem, also müssen Sie ITC verstehen, um die anderen zu schätzen. Lets Blick auf die Definition eines Forth Wortes SQUARE: In einem typischen ITC Forth Dies würde im Speicher erscheinen, wie in Abbildung 1 gezeigt. (Der Header wird in einem zukünftigen Artikel diskutiert es hält Haushalt Informationen für Compilation verwendet und ist nicht in Threading beteiligt .) Angenommen, SQUARE wird bei der Ausführung eines anderen Forth-Wortes angetroffen. Forths Interpreter Pointer (IP) wird auf eine Zelle im Speicher hinweisen - in diesem Quotenwort enthalten - das die Adresse des Wortes SQUARE enthält. (Um genau zu sein, enthält diese Zelle die Adresse des SQUAREs Code Field.) Der Interpreter holt diese Adresse und verwendet sie dann, um den Inhalt des SQUAREs Code Field zu holen. Diese Inhalte sind noch eine Adresse - die Adresse einer Maschinensprache Unterroutine, die das Wort SQUARE führt. In Pseudocode ist dies: Dies veranschaulicht ein wichtiges, aber selten aufgeklärtes Prinzip: Die Adresse des Forth Wortes, das gerade eingegeben wurde, wird in W gehalten. CODE Worte brauchen diese Information nicht, aber alle anderen Arten von Forth Worten. Wenn SQUARE in Maschinencode geschrieben wurde, wäre dies das Ende der Geschichte: das Bit des Maschinencodes würde ausgeführt werden, und dann springen Sie zurück zum Forth Interpreter - das, da IP inkrementiert wurde, zeigt auf das nächste Wort zu Hingerichtet werden Aus diesem Grund wird der Forth-Interpreter normalerweise NEXT genannt. Aber, SQUARE ist eine High-Level-Quollonquot-Definition - es hält ein quotthreadquot, eine Liste von Adressen. Um diese Definition durchzuführen, muss der Forth-Interpreter an einem neuen Ort neu gestartet werden: das Parameterfeld von SQUARE. Natürlich muss der alte Ort des Dolmetschers gespeichert werden, um das Quottwort weiterzuleiten, sobald SQUARE beendet ist. Das ist genau wie ein Unterprogramm-Aufruf Die Maschinensprache-Aktion von SQUARE ist einfach, die alte IP zu schieben, IP auf einen neuen Ort zu setzen, den Dolmetscher auszuführen, und wenn SQUARE fertig ist, (Wie Sie sehen können, ist die IP-Adresse das Zitat-Kontrapunkt von High-Level-Forth.) Dies wird DOCOLON oder ENTER in verschiedenen Forths genannt: Dieses identische Codefragment wird von allen hochrangigen (dh mit Gewinde versehenen) Forth-Definitionen verwendet. Das ist, warum ein Zeiger Zu diesem Code-Fragment, nicht das Fragment selbst, ist in der Forth-Definition enthalten. Über hunderte von Definitionen, die Einsparungen addieren Und das ist, warum seine genannt Indirekt Threading. Das Rückmeldung von subroutinequot ist das Wort EXIT, das kompiliert wird, wenn Forth sieht. (Einige Forths nennen es S anstelle von EXIT.) EXIT führt gerade eine Maschinensprache Routine, die die folgenden: Gehen Sie durch ein paar verschachtelte Forth Definitionen, nur um sicherzustellen, dass dies funktioniert. Beachten Sie die Eigenschaften von ITC: jedes Forth Wort hat ein einzelliges Codefeld. Colon-Definitionen kompilieren eine Zelle für jedes Wort, das in der Definition verwendet wird. Und der Forth-Interpreter muss eigentlich eine doppelte Indirektion ausführen, um die Adresse des nächsten Maschinencodes zu starten (zuerst über IP, dann durch W). ITC ist weder die kleinste noch die schnellste Threading-Technik. Es kann das einfachste sein, obwohl DTC (beschrieben als nächstes) wirklich nicht komplexer ist. Also warum sind so viele Forths indirekt-threaded Vor allem, weil früheren Forths, als Modelle verwendet wurden, wurden indirekt-threaded. In diesen Tagen wird DTC immer beliebter. Also wann sollte ITC verwendet werden Von den verschiedenen Techniken produziert ITC die saubersten und elegantesten Definitionen - nichts als Adressen. Wenn Sie auf solche Überlegungen abgestimmt sind, kann sich ITC an Sie wenden. Wenn Ihr Code mit dem Innere der Definitionen herumgeht, kann die Einfachheit und Einheitlichkeit der ITC-Darstellung die Portabilität verbessern. ITC ist das klassische Forth-Modell, also kann es für die Ausbildung bevorzugt sein. Schließlich, auf CPUs ohne Subroutine Anrufanweisung - wie die 1802 - ITC ist oft effizienter als DTC. Direct Threaded Code (DTC) Direct Threaded Code unterscheidet sich von ITC nur in einer Hinsicht: anstelle des Codefeldes, das die Adresse eines Maschinencodes enthält, enthält das Codefeld den aktuellen Maschinencode selbst. Ich sage nicht, dass der komplette Code für ENTER in jeder Dollon-Definition enthalten ist. In quothigh-levelquot Forth Worten enthält das Code-Feld einen Unterprogrammaufruf. Wie in Abbildung 2 dargestellt. Colon-Definitionen enthalten zum Beispiel einen Aufruf der ENTER-Routine. Der NEXT-Pseudocode für das direkte Threading ist einfach: Das gewinnt Geschwindigkeit: Der Dolmetscher führt nun nur eine einzige Indirektion durch. Auf der Z80 reduziert dies die NEXT-Routine - das meist genutzte Codefragment im Forth-Kernel - von elf Anleitungen bis zu sieben Das kostet Raum: Jede High-Level-Definition in einem Z80 Forth (zB) ist nun ein Byte länger, Da eine 2-Byte-Adresse durch einen 3-Byte-Aufruf ersetzt wurde. Aber das ist nicht allgemein wahr. Ein 32-Bit 68000 Forth kann eine 4-Byte-Adresse durch eine 4-Byte-BSR-Anweisung ersetzen, für keinen Nettoverlust. Und auf dem Zilog Super8, der Maschinenanweisungen für DTC Forth hat, wird die 2-Byte-Adresse durch eine 1-Byte-ENTER-Anweisung ersetzt, so dass ein DTC Forth kleiner auf dem Super8 ist. Natürlich sind die DTC-CODE-Definitionen zwei Bytes kürzer, da sie Ich brauche überhaupt keinen Zeiger mehr Ich dachte, dass High-Level-Definitionen in DTC Forths die Verwendung eines Unterprogrammaufrufs im Codefeld erforderten. Frank Sergeants Pygmy Forth SER90 zeigt, dass ein einfacher Sprung genauso einfach genutzt werden kann und in der Regel schneller ist. Guy Kelly hat eine hervorragende Überprüfung von Forth-Implementierungen für den IBM PC KEL92 zusammengestellt, den ich allen Forth-Kernel-Autoren sehr empfehlen kann. Von den 19 Forths, die er studierte, nutzten 10 DTC, 7 benutzte ITC und 2 benutzte Subroutine Threading (diskutiert als nächstes). Ich empfehle die Verwendung von Direct-Threaded Code über Indirect-Threaded Code für alle neuen Forth-Kernel. Springe zu NEXT, oder kodiere es in-line Der Forth innere Interpreter, NEXT, ist eine gemeinsame Routine zu allen CODE-Definitionen. Sie könnten nur eine Kopie dieser gemeinsamen Routine zu halten, und haben alle CODE Worte zu ihm springen. (Beachten Sie, dass Sie zu NEXT springen eine Unterroutine Aufruf ist nicht notwendig.) Allerdings ist die Geschwindigkeit von NEXT entscheidend für die Geschwindigkeit des gesamten Forth-Systems. Auch bei vielen CPUs ist die NEXT-Routine ziemlich oft oft nur zwei oder drei Anweisungen. So kann es vorzuziehen, NEXT in-line zu codieren, wo immer es benutzt wird. Dies geschieht häufig, indem man NEXT ein Assembler-Makro macht. Das ist eine einfache Geschwindigkeit vs. Raumentscheidung: Inline NEXT ist immer schneller, aber fast immer größer. Die Gesamtgrößenerhöhung ist die Anzahl der zusätzlichen Bytes, die für die Inline-Erweiterung erforderlich sind, mal die Anzahl der CODE-Wörter im System. Manchmal gibt es keine Kompromisse: In einem 6809 DTC Forth ist eine Inline-NEXT kürzer als ein Sprung-Befehl Subroutine Threaded Code (STC) Eine High-Level-Forth-Definition ist nichts als eine Liste von Subroutinen, die ausgeführt werden sollen. Sie brauchen nicht Dolmetscher, um dies zu erreichen, können Sie die gleiche Wirkung durch einfaches Anreißen einer Liste von Unterprogrammaufrufen zusammen: Siehe Abbildung 3. Diese Darstellung von Forth-Wörtern wurde als Ausgangspunkt verwendet, um Forth-Threading-Techniken für die Assembler-Programmierer KOG82 zu erklären. STC ist eine elegante Darstellung Colon Definitionen und CODE Worte sind jetzt identisch. DeDefined wordsquot (VARIABLEs, CONSTANTs und dergleichen) werden wie im DTC behandelt - das Codefeld beginnt mit einem Sprung oder einem Anruf zu irgendeinem Maschinencode an anderer Stelle. Der Hauptnachteil ist, dass Subroutine-Anrufe in der Regel größer als einfache Adressen sind. Auf der Z80 z. B. steigt die Größe der Doppelpunktdefinitionen um 50 - und die meisten deiner Anwendung sind Doppelpunktdefinitionen. Auf einer 32-Bit-68000 kann es überhaupt keine Größenerhöhung geben, wenn 4-Byte-Adressen ersetzt werden 4-Byte-BSRs. (Wenn aber Ihre Codegröße 64 KB überschreitet, müssen einige dieser Adressen durch 6-Byte-JSRs ersetzt werden.) Das Subroutine-Threading kann schneller sein als das direkte Threading. Sie sparen Zeit, indem Sie keinen Dolmetscher haben, aber Sie verlieren Zeit, weil jeder Hinweis auf ein Forth-Wort einen Push und Pop einer Rücksendeadresse beinhaltet. In einem DTC Forth verursachen nur hochrangige Wörter Aktivität auf dem Rücksprungstapel. Auf dem 6809 oder Zilog Super8 ist DTC schneller als STC. Es gibt einen weiteren Vorteil für STC: es verzichtet auf das IP-Register. Einige Prozessoren - wie die 8051 - sind verzweifelt an die Adressierung von Registern. Die Eliminierung der IP kann den Kernel wirklich vereinfachen und beschleunigen Der einzige Weg, um sicher zu wissen, ist, Beispielcode zu schreiben. Dies ist eng mit der Registerauswahl verbunden, im nächsten Abschnitt besprochen. STC mit Inline-Erweiterungsoptimierung Direktkompilierung Bei älteren und 8-Bit-CPUs beinhaltet fast jedes Forth-Primitiv mehrere Maschinenbefehle. Aber auf leistungsstärkeren CPUs, viele Forth Primitive sind in einer einzigen Anweisung geschrieben. Zum Beispiel, auf dem 32-Bit 68000, DROP ist einfach In einem Subroutine-Threaded Forth, mit DROP in einer Doppelpunkt-Definition würde in der Sequenz ADDQ ist eine Zwei-Byte-Anweisung führen. Warum einen 10-Byte-Unterprogramm-Aufruf zu einem Zwei-Byte-Befehl schreiben Egal wie oft DROP verwendet wird, theres keine Einsparungen Der Code ist kleiner und schneller, wenn der ADDQ direkt in den Strom von BSRs codiert wird. Einige Forth-Compiler machen diese Quinline-Erweiterung der CODE-Wörter CUR93a. Der Nachteil der Inline-Erweiterung ist, dass das Dekompilieren des ursprünglichen Quellcodes sehr schwierig wird. Solange Subroutine-Aufrufe verwendet werden, haben Sie immer noch Zeiger (die Unterroutinen-Adressen) zu den Forth-Wörtern, die den Thread enthalten. Mit Zeigern zu den Wörtern können Sie ihre Namen erhalten. Aber sobald ein Wort in Inline-Code erweitert wird, ist alles Wissen, woher dieser Code kam, verloren. Der Vorteil der Inline-Erweiterung - abgesehen von Geschwindigkeit und Größe - ist das Potenzial für Code-Optimierung. Zum Beispiel würde die Forth-Sequenz in 68000 STC kompiliert werden, aber könnte in-line als eine einzige Maschine Anweisung erweitert werden Optimierung Forth Compiler ist zu breit ein Thema für diesen Artikel. Dies ist ein aktiver Bereich der Forth Sprachforschung, zum Beispiel, SCO89 und CUR93b. Die endgültige Kulmination der optimierten STC ist ein Forth, die kompatibel zu quotpurequot Maschinencode, genau wie ein C oder Fortran Compiler. Token Threaded Code (TTC) DTC und STC zielen darauf ab, die Geschwindigkeit von Forth Programmen zu verbessern, mit einigen Kosten im Speicher. Jetzt können wir die andere Richtung von ITC bewegen, in Richtung etwas langsamer, aber kleiner. Der Zweck eines Forth-Threads ist es, eine Liste von Forth-Wörtern (Subroutinen) anzugeben, die ausgeführt werden sollen. Angenommen, ein 16-Bit-Forth-System hatte nur maximal 256 verschiedene Wörter. Dann könnte jedes Wort durch eine 8-Bit-Zahl eindeutig identifiziert werden. Statt einer Liste von 16-Bit-Adressen, hättest du eine Liste von 8-Bit-Bezeichnern oder Quottokens, und die Größe der Doppelpunktdefinitionen wäre halbiert. Ein Token-Threaded Forth hält eine Tabelle von Adressen aller Forth Wörter, wie Gezeigt in Fig. 4. Der Token-Wert wird dann verwendet, um in diese Tabelle zu indexieren, um das Forth-Wort zu finden, das einem gegebenen Token entspricht. Dies fügt eine Ebene der Indirektion für den Forth-Interpreter hinzu, also ist es langsamer als ein quotaddress-threadedquot Forth. Der Hauptvorteil von Token-Threaded Forths ist klein. TTC wird am häufigsten in Handheld-Computern und anderen stark beschränkten Anwendungen gesehen. Auch die Tabelle der Quotienten platziert in alle Forth Worte vereinfachen die Verknüpfung von separat kompilierten Modulen. Der Nachteil von TTC ist Geschwindigkeit: TTC macht die langsamsten Forths. Auch der TTC-Compiler ist etwas komplexer. Wenn du mehr als 256 Forth Worte brauchst, ist es notwendig, ein offenes Codierungsschema zu haben, um 8-Bit - und größere Token zu mischen. Ich kann mir vorstellen, dass ein 32-Bit-Forth mit 16-Bit-Token, aber wie viele 32-Bit-Systeme sind size-constrained Segment Threaded Code Da gibt es so viele 8086 Derivate in der Welt, Segment Threading verdient eine kurze Erwähnung. Anstelle der Verwendung von quotnormalquot-Byteadressen innerhalb eines 64K-Segments werden Absatzadressen verwendet. (Ein Quittungsquottel ist 16 Bytes im 8086.) Dann kann der Interpreter diese Adressen in Segmentregister laden, anstatt in die üblichen Adressregister. Dies ermöglicht ein 16-Bit-Forth-Modell, um effizient auf das volle Megabyte des 8086-Speichers zuzugreifen. Der Hauptnachteil des Segmentgewindeschneidens ist der 16-Byte-Quotenbildungsraum des Speicherplatzes. Jedes Wort muss auf eine 16-Byte-Grenze ausgerichtet sein. Wenn Forth-Wörter zufällige Längen haben, wird ein Durchschnitt von 8 Bytes pro Forth-Wort verschwendet. REGISTER ALLOCATION Neben der Threading-Technik ist die Verwendung der CPUs-Register die wichtigste Designentscheidung. Es ist wahrscheinlich das schwierigste. Die Verfügbarkeit von CPU-Registern kann bestimmen, welche Threading-Technik verwendet werden kann, und sogar, was die Speicherkarte sein wird Die klassischen Forth-Register Das klassische Forth-Modell hat fünf quadratische Register. Es handelt sich um abstrakte Entitäten, die in den primitiven Operationen von Forth verwendet werden. NEXT, ENTER und EXIT wurden früher in Bezug auf diese abstrakten Register definiert. Jeder von diesen ist eine Zelle breit - d. h. in einem 16-Bit-Forth, das sind 16-Bit-Register. (Es gibt Ausnahmen von dieser Regel, wie Sie später sehen werden.) Diese können nicht alle CPU-Register sein. Wenn Ihre CPU nicht genug Register hat, können einige von ihnen im Speicher gehalten werden. Ich beschreibe sie in der Reihenfolge ihrer Bedeutung, d. h. die Unterseite dieser Liste sind die besten Kandidaten, die im Gedächtnis gespeichert werden sollen. W ist das Arbeitsregister. Es wird für viele Dinge verwendet, einschließlich Speicherreferenz, also sollte es ein Adressregister sein, d. h. Sie müssen in der Lage sein, Speicher zu speichern und Speicher mit dem Inhalt von W als die Adresse zu speichern. Du musst auch in der Lage sein, Arithmetik auf W. (In DTC Forths, du musst auch in der Lage sein, indirekt mit W zu springen.) W wird von dem Dolmetscher in jedem Forth Wort verwendet. In einer CPU, die nur ein Register hat, würdest du es für W verwenden und alles andere im Gedächtnis halten (und das System wäre unglaublich langsam). IP ist der Interpreter Pointer. Dies wird von jedem Forth-Wort (über NEXT, ENTER oder EXIT) verwendet. IP muss ein Adressbuch sein. Du musst auch in der Lage sein, IP zu erhöhen. Subroutine threaded Forths brauchen nicht dieses Register. PSP ist der Parameter Stack (oder quotdata stackquot) Zeiger, manchmal auch einfach SP genannt. Ich bevorzuge PSP, weil SP häufig der Name eines CPU-Registers ist, und sie sollten nicht verwirrt werden. Die meisten CODE-Wörter verwenden das. PSP muss ein Stapelzeiger sein oder ein Adressregister, das inkrementiert und dekrementiert werden kann. Es ist auch ein Plus, wenn man indizierte Adressierung von PSP machen kann. RSP ist der Return Stack Pointer, manchmal auch einfach RP genannt. Dies wird von Doppelpunktdefinitionen in ITC und DTC Forths und bei allen Wörtern in STC Forths verwendet. RSP muss ein Stapelzeiger oder ein Adressregister sein, das inkrementiert und dekrementiert werden kann. Wenn überhaupt möglich . Setzen W, IP, PSP und RSP in Registern. Die virtuellen Register, die folgen, können im Speicher gehalten werden, aber es gibt normalerweise einen Geschwindigkeitsvorteil, um sie in CPU-Registern zu halten. X ist ein Arbeitsregister, das nicht als eines der quotclassicalquot Forth Register betrachtet wird, obwohl das klassische ITC Forths es für die zweite Indirektion benötigt. In ITC müssen Sie in der Lage sein, indirekt mit X zu springen. X kann auch von einigen CODE-Wörtern verwendet werden, um Arithmetik und so zu tun. Dies ist besonders wichtig für Prozessoren, die kein Speicher als Operand verwenden können. Zum Beispiel könnte ADD auf einem Z80 sein (im Pseudocode) Manchmal ist auch ein anderes Arbeitsregister Y definiert. UP ist der User Pointer, der die Basisadresse des Task-User-Bereichs hält. UP wird in der Regel zu einem Offset hinzugefügt und wird von High-Level-Forth-Code verwendet, so dass es nur irgendwo gespeichert werden kann. Aber wenn die CPU indizierte Adressierung aus dem UP-Register ausführen kann, können CODE-Wörter einfacher und schneller auf Benutzervariablen zugreifen. Wenn Sie einen Überschuss von Adressregistern haben, verwenden Sie einen für UP. Single-Task Forths brauchen nicht UP. X - wenn nötig - ist wichtiger, sich zu registrieren als UP. UP ist die einfachste der virtuellen Register, um in den Speicher zu gelangen. Verwendung des Hardware-Stacks Die meisten CPUs haben einen Stack-Pointer als Teil ihrer Hardware, die von Interrupts und Unterprogrammaufrufen verwendet wird. Wie ist diese Karte in die Forth-Register Sollte es die PSP oder die RSP Die kurze Antwort ist, es hängt davon ab. Es wird gesagt, dass die PSP mehr als die RSP in ITC und DTC Forths verwendet wird. Wenn Ihre CPU nur wenige Adressregister hat und PUSH und POP schneller sind als explizite Referenz, verwenden Sie den Hardware-Stack als Parameter Stack. Auf der anderen Seite, wenn Ihre CPU ist reich an Adressierung Modi - und ermöglicht indizierte Adressierung - theres ein Plus in die PSP als Allzweck-Adresse registrieren. Verwenden Sie in diesem Fall den Hardware-Stack als Return Stack. Manchmal machst du weder den TMS320C25s Hardware Stack nur acht Zellen tief - alles aber nutzlos für Forth. So wird sein Hardware-Stack nur für Interrupts verwendet, und sowohl PSP als auch RSP sind Allzweck-Adressregister. (ANS Forth spezifiziert ein Minimum von 32 Zellen von Parameter Stack und 24 Zellen von Return Stack Ich bevorzuge 64 Zellen von jedem.) Sie werden gelegentlich begegnen das Dogma, dass die Hardware-Stack-Quotte bequot der Parameter Stack, oder quotmust bequot der Return Stack. Stattdessen Code einige Probe Forth Primitiven, wie und sehen, welche Ansatz ist kleiner oder schneller. (DUP und DROP, übrigens sind kein Test - sie sind in der Regel trivial.) Gelegentlich erreichen Sie seltsame Schlussfolgerungen Gary Bergstrom hat darauf hingewiesen, dass ein 6809 DTC Forth kann ein paar Zyklen schneller gemacht werden, indem Sie die 6809 Benutzer Stack Zeiger als die IP NEXT wird zum POP. Er verwendet ein Indexregister für einen der Forths-Stacks. Top-of-Stack in Register Forths Leistung kann erheblich verbessert werden, indem man das obere Element des Parameters Stack in einem Register Viele Forth Wörter (wie 0) dann nicht brauchen, um den Stack zu verwenden. Andere Worte machen immer noch die gleiche Anzahl von Pushs und Pops, nur an einem anderen Ort im Code. Nur wenige Forth-Wörter (DROP und 2DROP) werden komplizierter, da man den Stack-Pointer nicht mehr einfach einstellen kann - man muss auch das TOS-Register aktualisieren. Es gibt ein paar Regeln beim Schreiben von CODE-Wörtern: Ein Wort, das Elemente aus dem Stack entfernt, muss das Quell-TOS in sein Register platzieren. Ein Wort, das dem Stapel Gegenstände hinzufügt, muss das Quotto-TOS auf den Stapel schieben (es sei denn, es ist natürlich das verbraucht durch das Wort). Wenn Sie mindestens sechs Zellen-CPU-Register haben, empfehle ich, die TOS in einem Register zu halten. Ich betrachte TOS wichtiger als UP, um mich zu registrieren, aber weniger wichtig als W, IP, PSP und RSP. (TOS im Register führt viele der Funktionen des X-Registers aus.) Es ist nützlich, wenn dieses Register die Speicheradressierung durchführen kann. PDP-11s, Z8s und 68000s sind gute Kandidaten. Neun der 19 IBM PC Forths studiert von Guy Kelly KEL92 halten TOS in Register. Ich denke, diese Neuerung wurde wegen der falschen Überzeugungen widerstanden, dass a) sie Anweisungen hinzufügt und b) das obere Stapelelement als Speicher zugänglich sein muss. Es stellt sich heraus, dass auch solche Worte wie PICK, ROLL und DEPTH für TOS-in-Register trivial geändert werden. Was ist mit dem Puffern von zwei Stapelelementen in Registern Wenn Sie die Oberseite des Stapels in einem Register halten, bleibt die Gesamtzahl der Operationen im Wesentlichen gleich. Ein Push bleibt ein Push, egal ob es vor oder nach der Operation, die Sie durchführen. Auf der anderen Seite fügt das Puffern von zwei Stapelelementen in Registern eine große Anzahl von Befehlen hinzu - ein Push wird ein Push, gefolgt von einer Bewegung. Nur dedizierte Forth-Prozessoren wie der RTX2000 und fantastisch clever optimierende Compiler können von der Pufferung von zwei Stack-Elementen in Registern profitieren. Einige Beispiele Hier sind die Registerzuweisungen von Forths für eine Anzahl verschiedener CPUs. Versuchen Sie, die Designentscheidungen der Autoren aus dieser Liste abzuleiten. QuotSPquot bezieht sich auf den Hardware-Stack-Pointer. QuotZpagequot bezieht sich auf Werte, die in der 6502s-Speicherseite Null gehalten werden, die fast genauso nützlich sind wie - manchmal nützlicher als - Werte, die in Registern, z. B. Sie können für die Speicheradressierung verwendet werden. QuotFixedquot bedeutet, dass Paynes 8051 Forth einen einzigen, unbeweglichen Benutzerbereich hat und UP eine hartcodierte Konstante ist. Schmale Register Beachten Sie etwas Ungewöhnliches in der vorherigen Liste Das 6502 Forth - ein 16-Bit-Modell - verwendet 8-Bit-Stack-Zeiger Es ist möglich, PSP, RSP und UP kleiner als die Zellengröße des Forth zu machen. Dies liegt daran, dass die Stacks und der Benutzerbereich beide relativ kleine Speicherbereiche sind. Jeder Stapel kann so klein sein wie 64 Zellen in der Länge, und der Benutzerbereich übersteigt selten 128 Zellen. Sie müssen lediglich sicherstellen, dass entweder a) diese Datenbereiche auf einen kleinen Speicherbereich beschränkt sind, so dass eine kurze Adresse verwendet werden kann oder b) die hohen Adreßbits auf andere Weise bereitgestellt werden, z. B. Eine Speicherseite auswählen. Im 6502 ist der Hardware-Stack durch die Gestaltung der CPU auf Seite eins des RAM (Adressen 01xxh) beschränkt. Der 8-Bit-Stack-Pointer kann für den Return Stack verwendet werden. Der Parameter Stack wird auf der Seite Null des RAM gehalten, auf den indirekt durch das 8-Bit-Indexregister X zugegriffen werden kann. (Frage für den fortgeschrittenen Schüler: Warum das 6502s X verwenden und nicht Y Hinweis: Schau dir die verfügbaren Adressierungsmodi an. ) Im 8051 können Sie die 8-Bit-Register R0 und R1 verwenden, um das externe RAM zu adressieren, vorausgesetzt, dass Sie die hohen 8 Bits der Adresse explizit an Port 2 ausgeben. Dies ermöglicht eine Rückmeldung für zwei Stacks. UP unterscheidet sich von PSP und RSP: Es gibt einfach eine Basisadresse, die es niemals inkrementiert oder dekrementiert wird. So ist es praktisch, nur die hohen Bits dieses virtuellen Registers zu liefern. Die niedrigen Bits müssen dann von einer beliebigen indizierten Adressierungstechnik bereitgestellt werden. Zum Beispiel können Sie auf dem 6809 das DP-Register verwenden, um die hohen 8 Bits von UP zu halten und dann die Direct Page Adressierung zu verwenden, um auf eine der 256 Standorte auf dieser Seite zuzugreifen. Dies zwingt alle Benutzerbereiche, an einer Adresse xx00h zu beginnen, was keine große Härte ist und den Benutzerbereich auf 128 Zellen begrenzt. Auf dem 8086 können Sie ein Segmentregister vorstellen, um die Basisadresse des Benutzerbereichs anzugeben. REFERENZEN CUR93a Curley, Charles, quotLife in der FastForth Lane, die auf die Veröffentlichung in Forth Dimensions wartet. Beschreibung eines 68000 Subroutine-Threaded Forth. CUR93b Curley, Charles, quotOptimierung in einem BSRJSR Threaded Forth, in Erwartung der Veröffentlichung in Forth Dimensions. Single-Pass-Code-Optimierung für FastForth, in nur fünf Bildschirmen Code Inklusive Auflistung. KEL92 Kelly, Guy M. quotForth Systems Vergleiche, Forth Dimensionen XIII: 6 (MarApr 1992). Auch veröffentlicht in der 1991 FORML Conference Proceedings. Beide sind bei der Forth Interest Group, P. O. Box 2154, Oakland, CA 94621. Illustriert Design Kompromisse von vielen 8086 Forths mit Code-Fragmenten und Benchmarks - sehr empfehlenswert KOG82 Kogge, Peter M. quotAon Architectural Trail zu Threaded-Code-Systeme, IEEE Computer, vol. 15 nein 3 (März 1982). Bleibt die endgültige Beschreibung der verschiedenen Threading-Techniken. ROD91 Rodriguez, B. J.B. Y.O. Assembler, Teil 1, The Computer Journal 52 (SepOkt 1991). Allgemeine Grundsätze des Schreibens von Forth Assemblern. ROD92 Rodriguez, B. J.B. Y.O. Assembler, Teil 2, The Computer Journal 54 (JanFeb 1992). Ein 6809 Assembler in Forth. SCO89 Scott, Andrew, quotAn Extensible Optimizer für die Kompilierung von Forth, 1989 ForML Conference Proceedings. Forth Interest Group, P. O. Box 2154, Oakland, CA 94621. Gute Beschreibung eines 68000 Optimierers ohne Code. CUR86 Curley, Charles, real-Forth für die 68000. Privat verteilt (1986). JAM80 James, John S. fig-Forth für den PDP-11. Forth Interest Group (1980). KUN81 Kuntze, Robert E. MVP-Forth für den Apple II. Mountain View Press (1981). LAX84 Laxen, H. und Perry, M. F83 für den IBM PC. Version 2.1.0 (1984). Verteilt durch die Autoren, erhältlich von der Forth Interest Group oder GEnie. LOE81 Loeliger, R. G. Threaded Interpretive Sprachen. BYTE Publikationen (1981), ISBN 0-07-038360-X. Kann das einzige Buch sein, das jemals über das Thema der Schaffung eines Forth-artigen Kernels geschrieben wurde (das verwendete Beispiel ist der Z80). Es lohnt sich, wenn man eine Kopie finden kann. MPE92 MicroProcessor Engineering Ltd. MPE Z8Super8 PowerForth Target. MPE Ltd. 133 Hill Lane, Shirley, Southampton, S01 5AF, U. K. (Juni 1992). Ein kommerzielles Produkt. PAY90 Payne, William H. Embedded Controller FORTH für die 8051 Familie. Academic Press (1990), ISBN 0-12-547570-5. Dies ist ein komplettes quotkitquot für einen 8051 Forth, einschließlich eines metacompilers für den IBM PC. Hardcopy nur Dateien können von GEnie heruntergeladen werden. Nicht für den Anfänger SER90 Sergeant, Frank, Pygmy Forth für den IBM PC. Version 1.3 (1990). Verteilt durch den Autor, erhältlich von der Forth Interest Group. Version 1.4 ist jetzt auf GEnie verfügbar und lohnt sich die zusätzliche Anstrengung zu erhalten. TAL80 Talbot, R. J. Fig-Forth für die 6809. Forth Interest Group (1980). Autoren beachten für die Webpublikation: Die bisher auf dem GEnie Online-Service verfügbaren Dateien sind ab sofort bei der Forth-Interessengruppe FTP-Server ftp. forth. orgpubForth. THE NATURE OF CODE Die Art of Code Einleitung Ich bin zwei mit der Natur. Woody Allen Hier sind wir: der Anfang. Nun, fast der Anfang. Wenn es eine Weile her ist, seit Sie irgendwelche Programmierung in der Verarbeitung (oder irgendeine Mathematik, für diese Angelegenheit) getan haben, wird diese Einleitung Ihren Verstand zurück in das rechnerische Denken erhalten, bevor wir einige der schwierigeren und komplexen Materialien nähern. In Kapitel 1 würden wir über das Konzept eines Vektors sprechen und wie es als Baustein für die Simulation der Bewegung in diesem Buch dienen wird. Aber bevor wir diesen Schritt machen, können wir darüber nachdenken, was es für etwas bedeutet, einfach um den Bildschirm zu bewegen. Lasst uns mit einer der bekanntesten und einfachsten Simulationen der Bewegung auf den zufälligen Spaziergang beginnen. I.1 Zufällige Spaziergänge Stellen Sie sich vor, Sie stehen in der Mitte eines Balance-Balken. Alle zehn Sekunden spiegeln Sie eine Münze. Köpfe, mach einen Schritt vorwärts. Schwänze, mach einen Schritt zurück. Dies ist ein zufälliger Walka-Pfad, der als eine Reihe von zufälligen Schritten definiert ist. Stepping off that balance beam and onto the floor, you could perform a random walk in two dimensions by flipping that same coin twice with the following results: Yes, this may seem like a particularly unsophisticated algorithm. Nevertheless, random walks can be used to model phenomena that occur in the real world, from the movements of molecules in a gas to the behavior of a gambler spending a day at the casino. As for us, we begin this book studying a random walk with three goals in mind. We need to review a programming concept central to this bookobject-oriented programming. The random walker will serve as a template for how we will use object-oriented design to make things that move around a Processing window. The random walk instigates the two questions that we will ask over and over again throughout this book: How do we define the rules that govern the behavior of our objects and then, How do we implement these rules in Processing Throughout the book, well periodically need a basic understanding of randomness, probability, and Perlin noise. The random walk will allow us to demonstrate a few key points that will come in handy later. I.2 The Random Walker Class Lets review a bit of object-oriented programming (OOP) first by building a Walker object. This will be only a cursory review. If you have never worked with OOP before, you may want something more comprehensive Id suggest stopping here and reviewing the basics on the Processing website before continuing. An object in Processing is an entity that has both data and functionality. We are looking to design a Walker object that both keeps track of its data (where it exists on the screen) and has the capability to perform certain actions (such as draw itself or take a step). A class is the template for building actual instances of objects. Think of a class as the cookie cutter the objects are the cookies themselves. Lets begin by defining the Walker classwhat it means to be a Walker object. The Walker only needs two pieces of dataa number for its x-location and one for its y-location. Objects have data. Since we only draw the background once in setup(). rather than clearing it continually each time through draw(). we see the trail of the random walk in our Processing window. Your browser does not support the canvas tag. There are a couple improvements we could make to the random walker. For one, this Walker s step choices are limited to four optionsup, down, left, and right. But any given pixel in the window has eight possible neighbors, and a ninth possibility is to stay in the same place. To implement a Walker object that can step to any neighboring pixel (or stay put), we could pick a number between 0 and 8 (nine possible choices). However, a more efficient way to write the code would be to simply pick from three possible steps along the x-axis (-1, 0, or 1) and three possible steps along the y-axis. Yields -1, 0, or 1 All of these variations on the traditional random walk have one thing in common: at any moment in time, the probability that the Walker will take a step in a given direction is equal to the probability that the Walker will take a step in any direction. In other words, if there are four possible steps, there is a 1 in 4 (or 25) chance the Walker will take any given step. With nine possible steps, its a 1 in 9 (or 11.1) chance. Conveniently, this is how the random() function works. Processings random number generator (which operates behind the scenes) produces what is known as a uniform distribution of numbers. We can test this distribution with a Processing sketch that counts each time a random number is picked and graphs it as the height of a rectangle. Your browser does not support the canvas tag. Example I.2: Random number distribution An array to keep track of how often random numbers are picked Pick a random number and increase the count. Graphing the results The above screenshot shows the result of the sketch running for a few minutes. Notice how each bar of the graph differs in height. Our sample size (i. e. the number of random numbers weve picked) is rather small and there are some occasional discrepancies, where certain numbers are picked more often. Over time, with a good random number generator, this would even out. Pseudo-Random Numbers The random numbers we get from the random() function are not truly random therefore they are known as pseudo-random. They are the result of a mathematical function that simulates randomness. This function would yield a pattern over time, but that time period is so long that for us, its just as good as pure randomness Exercise I.1 Create a random walker that has a tendency to move down and to the right. (Well see the solution to this in the next section.) I.3 Probability and Non-Uniform Distributions Remember when you first started programming in Processing Perhaps you wanted to draw a lot of circles on the screen. So you said to yourself: Oh, I know. Ill draw all these circles at random locations, with random sizes and random colors. In a computer graphics system, its often easiest to seed a system with randomness. In this book, however, were looking to build systems modeled on what we see in nature. Defaulting to randomness is not a particularly thoughtful solution to a design problemin particular, the kind of problem that involves creating an organic or natural-looking simulation. With a few tricks, we can change the way we use random() to produce non-uniform distributions of random numbers. This will come in handy throughout the book as we look at a number of different scenarios. When we examine genetic algorithms, for example, well need a methodology for performing selectionwhich members of our population should be selected to pass their DNA to the next generation Remember the concept of survival of the fittest Lets say we have a population of monkeys evolving. Not every monkey will have a equal chance of reproducing. To simulate Darwinian evolution, we cant simply pick two random monkeys to be parents. We need the more fit ones to be more likely to be chosen. We need to define the probability of the fittest. For example, a particularly fast and strong monkey might have a 90 chance of procreating, while a weaker one has only a 10 chance. Lets pause here and take a look at probabilitys basic principles. First well examine single event probability, i. e. the likelihood that a given event will occur. If you have a system with a certain number of possible outcomes, the probability of the occurrence of a given event equals the number of outcomes that qualify as that event divided by the total number of all possible outcomes. A coin toss is a simple exampleit has only two possible outcomes, heads or tails. There is only one way to flip heads. The probability that the coin will turn up heads, therefore, is one divided by two: 12 or 50. Take a deck of fifty-two cards. The probability of drawing an ace from that deck is: number of aces number of cards 4 52 0.077 The probability of drawing a diamond is: number of diamonds number of cards 13 52 0.25 25 We can also calculate the probability of multiple events occurring in sequence. To do this, we simply multiply the individual probabilities of each event. The probability of a coin turning up heads three times in a row is: (12) (12) (12) 18 (or 0.125) meaning that a coin will turn up heads three times in a row one out of eight times (each time being three tosses). Exercise I.2 What is the probability of drawing two aces in a row from a deck of fifty-two cards There are a couple of ways in which we can use the random() function with probability in code. One technique is to fill an array with a selection of numberssome of which are repeatedthen choose random numbers from that array and generate events based on those choices. 1 is stored in the array twice, making it more likely to be picked. Exercise I.3 Create a random walker with dynamic probabilities. For example, can you give it a 50 chance of moving in the direction of the mouse I.4 A Normal Distribution of Random Numbers Lets go back to that population of simulated Processing monkeys. Your program generates a thousand Monkey objects, each with a height value between 200 and 300 (as this is a world of monkeys that have heights between 200 and 300 pixels). Does this accurately depict the heights of real-world beings Think of a crowded sidewalk in New York City. Pick any person off the street and it may appear that their height is random. Nevertheless, its not the kind of random that random() produces. Peoples heights are not uniformly distributed there are a great deal more people of average height than there are very tall or very short ones. To simulate nature, we may want it to be more likely that our monkeys are of average height (250 pixels), yet still allow them to be, on occasion, very short or very tall. A distribution of values that cluster around an average (referred to as the mean) is known as a normal distribution. It is also called the Gaussian distribution (named for mathematician Carl Friedrich Gauss) or, if you are French, the Laplacian distribution (named for Pierre-Simon Laplace). Both mathematicians were working concurrently in the early nineteenth century on defining such a distribution. When you graph the distribution, you get something that looks like the following, informally known as a bell curve: The curve is generated by a mathematical function that defines the probability of any given value occurring as a function of the mean (often written as , the Greek letter mu ) and standard deviation (, the Greek letter sigma ). The mean is pretty easy to understand. In the case of our height values between 200 and 300, you probably have an intuitive sense of the mean (i. e. average) as 250. However, what if I were to say that the standard deviation is 3 or 15 What does this mean for the numbers The graphs above should give us a hint. The graph on the left shows us the distribution with a very low standard deviation, where the majority of the values cluster closely around the mean. The graph on the right shows us a higher standard deviation, where the values are more evenly spread out from the average. The numbers work out as follows: Given a population, 68 of the members of that population will have values in the range of one standard deviation from the mean, 98 within two standard deviations, and 99.7 within three standard deviations. Given a standard deviation of 5 pixels, only 0.3 of the monkey heights will be less than 235 pixels (three standard deviations below the mean of 250) or greater than 265 pixels (three standard deviations above the mean of 250). Calculating Mean and Standard Deviation Consider a class of ten students who receive the following scores (out of 100) on a test: 85, 82, 88, 86, 85, 93, 98, 40, 73, 83 The standard deviation is calculated as the square root of the average of the squares of deviations around the mean. In other words, take the difference from the mean for each person and square it (variance). Calculate the average of all these values and take the square root as the standard deviation. The standard deviation is the square root of the average variance: 15.13 Luckily for us, to use a normal distribution of random numbers in a Processing sketch, we dont have to do any of these calculations ourselves. Instead, we can make use of a class known as Random. which we get for free as part of the default Java libraries imported into Processing (see the JavaDocs for more information). To use the Random class, we must first declare a variable of type Random and create the Random object in setup() . We use the variable name generator because what we have here can be thought of as a random number generator. If we want to produce a random number with a normal (or Gaussian) distribution each time we run through draw(). its as easy as calling the function nextGaussian() . Asking for a Gaussian random number. (Note nextGaussian() returns a double and must be converted to float.) Heres the thing. What are we supposed to do with this value What if we wanted to use it, for example, to assign the x-position of a shape we draw on screen The nextGaussian() function returns a normal distribution of random numbers with the following parameters: a mean of zero and a standard deviation of one . Lets say we want a mean of 320 (the center horizontal pixel in a window of width 640) and a standard deviation of 60 pixels. We can adjust the value to our parameters by multiplying it by the standard deviation and adding the mean. Your browser does not support the canvas tag. Example I.4: Gaussian distribution Note that nextGaussian() returns a double. Multiply by the standard deviation and add the mean. By drawing the ellipses on top of each other with some transparency, we can actually see the distribution. The brightest spot is near the center, where most of the values cluster, but every so often circles are drawn farther to the right or left of the center. Exercise I.4 Consider a simulation of paint splatter drawn as a collection of colored dots. Most of the paint clusters around a central location, but some dots do splatter out towards the edges. Can you use a normal distribution of random numbers to generate the locations of the dots Can you also use a normal distribution of random numbers to generate a color palette Exercise I.5 A Gaussian random walk is defined as one in which the step size (how far the object moves in a given direction) is generated with a normal distribution. Implement this variation of our random walk. I.5 A Custom Distribution of Random Numbers There will come a time in your life when you do not want a uniform distribution of random values, or a Gaussian one. Lets imagine for a moment that you are a random walker in search of food. Moving randomly around a space seems like a reasonable strategy for finding something to eat. After all, you dont know where the food is, so you might as well search randomly until you find it. The problem, as you may have noticed, is that random walkers return to previously visited locations many times (this is known as oversampling). One strategy to avoid such a problem is to, every so often, take a very large step. This allows the walker to forage randomly around a specific location while periodically jumping very far away to reduce the amount of oversampling. This variation on the random walk (known as a Lvy flight) requires a custom set of probabilities. Though not an exact implementation of a Lvy flight, we could state the probability distribution as follows: the longer the step, the less likely it is to be picked the shorter the step, the more likely. Earlier in this prologue, we saw that we could generate custom probability distributions by filling an array with values (some duplicated so that they would be picked more frequently) or by testing the result of random(). We could implement a Lvy flight by saying that there is a 1 chance of the walker taking a large step. A 1 chance of taking a large step However, this reduces the probabilities to a fixed number of options. What if we wanted to make a more general rulethe higher a number, the more likely it is to be picked 3.145 would be more likely to be picked than 3.144, even if that likelihood is just a tiny bit greater. In other words, if x is the random number, we could map the likelihood on the y-axis with y x . If we can figure out how to generate a distribution of random numbers according to the above graph, then we will be able to apply the same methodology to any curve for which we have a formula. One solution is to pick two random numbers instead of one. The first random number is just that, a random number. The second one, however, is what well call a qualifying random value. It will tell us whether to use the first one or throw it away and pick another one. Numbers that have an easier time qualifying will be picked more often, and numbers that rarely qualify will be picked infrequently. Here are the steps (for now, lets consider only random values between 0 and 1): Pick a random number: R1 Compute a probability P that R1 should qualify. Lets try: P R1. Pick another random number: R2 If R2 is less than P, then we have found our numberR1 If R2 is not less than P, go back to step 1 and start over. Here we are saying that the likelihood that a random value will qualify is equal to the random number itself. Lets say we pick 0.1 for R1. This means that R1 will have a 10 chance of qualifying. If we pick 0.83 for R1 then it will have a 83 chance of qualifying. The higher the number, the greater the likelihood that we will actually use it. Here is a function (named for the Monte Carlo method, which was named for the Monte Carlo casino) that implements the above algorithm, returning a random value between 0 and 1. We do this forever until we find a qualifying random value. Exercise I.6 Use a custom probability distribution to vary the size of a step taken by the random walker. The step size can be determined by influencing the range of values picked. Can you map the probability exponentiallyi. e. making the likelihood that a value is picked equal to the value squared A uniform distribution of step sizes. Change this (Later well see how to do this more efficiently using vectors.) I.6 Perlin Noise (A Smoother Approach) A good random number generator produces numbers that have no relationship and show no discernible pattern. As we are beginning to see, a little bit of randomness can be a good thing when programming organic, lifelike behaviors. However, randomness as the single guiding principle is not necessarily natural. An algorithm known as Perlin noise, named for its inventor Ken Perlin, takes this concept into account. Perlin developed the noise function while working on the original Tron movie in the early 1980s it was designed to create procedural textures for computer-generated effects. In 1997 Perlin won an Academy Award in technical achievement for this work. Perlin noise can be used to generate various effects with natural qualities, such as clouds, landscapes, and patterned textures like marble. Perlin noise has a more organic appearance because it produces a naturally ordered (smooth) sequence of pseudo-random numbers. The graph on the left below shows Perlin noise over time, with the x-axis representing time note the smoothness of the curve. The graph on the right shows pure random numbers over time. (The code for generating these graphs is available in the accompanying book downloads.) Figure I.5: Noise Figure I.6: Random Processing has a built-in implementation of the Perlin noise algorithm: the function noise(). The noise() function takes one, two, or three arguments, as noise is computed in one, two, or three dimensions. Lets start by looking at one-dimensional noise. Noise Detail The Processing noise reference tells us that noise is calculated over several octaves. Calling the noiseDetail() function will change both the number of octaves and their importance relative to one another. This in turn changes how the noise function behaves. An online lecture by Ken Perlin lets you learn more about how noise works from Perlin himself . Consider drawing a circle in our Processing window at a random x-location. A random x-location Now, instead of a random x-location, we want a Perlin noise x-location that is smoother. You might think that all you need to do is replace random() with noise(). i. e. A noise x-location While conceptually this is exactly what we want to docalculate an x-value that ranges between 0 and the width according to Perlin noisethis is not the correct implementation. While the arguments to the random() function specify a range of values between a minimum and a maximum, noise() does not work this way. Instead, the output range is fixedit always returns a value between 0 and 1. Well see in a moment that we can get around this easily with Processings map() function, but first we must examine what exactly noise() expects us to pass in as an argument. We can think of one-dimensional Perlin noise as a linear sequence of values over time. For example: How quickly we increment t also affects the smoothness of the noise. If we make large jumps in time, then we are skipping ahead and the values will be more random. Try running the code several times, incrementing t by 0.01, 0.02, 0.05, 0.1, 0.0001, and you will see different results. Mapping Noise Now were ready to answer the question of what to do with the noise value. Once we have the value with a range between 0 and 1, its up to us to map that range to what we want. The easiest way to do this is with Processings map() function. The map() function takes five arguments. First up is the value we want to map, in this case n. Then we have to give it the values current range (minimum and maximum), followed by our desired range. In this case, we know that noise has a range between 0 and 1, but wed like to draw our circle with a range between 0 and the windows width. Notice how the above example requires an additional pair of variables: tx and ty. This is because we need to keep track of two time variables, one for the x-location of the Walker object and one for the y-location. But there is something a bit odd about these variables. Why does tx start at 0 and ty at 10,000 While these numbers are arbitrary choices, we have very specifically initialized our two time variables with different values. This is because the noise function is deterministic: it gives you the same result for a specific time t each and every time. If we asked for the noise value at the same time t for both x and y. then x and y would always be equal, meaning that the Walker object would only move along a diagonal. Instead, we simply use two different parts of the noise space, starting at 0 for x and 10,000 for y so that x and y can appear to act independently of each other. In truth, there is no actual concept of time at play here. Its a useful metaphor to help us understand how the noise function works, but really what we have is space, rather than time. The graph above depicts a linear sequence of noise values in a one-dimensional space, and we can ask for a value at a specific x-location whenever we want. In examples, you will often see a variable named xoff to indicate the x-offset along the noise graph, rather than t for time (as noted in the diagram). Exercise I.7 In the above random walker, the result of the noise function is mapped directly to the Walker s location. Create a random walker where you instead map the result of the noise() function to a Walker s step size. Two-Dimensional Noise This idea of noise values living in a one-dimensional space is important because it leads us right into a discussion of two-dimensional space. Lets think about this for a moment. With one-dimensional noise, we have a sequence of values in which any given value is similar to its neighbor. Because the value is in one dimension, it only has two neighbors: a value that comes before it (to the left on the graph) and one that comes after it (to the right). Figure I.10: 1D Noise Figure I.11: 2D Noise Two-dimensional noise works exactly the same way conceptually. The difference of course is that we arent looking at values along a linear path, but values that are sitting on a grid. Think of a piece of graph paper with numbers written into each cell. A given value will be similar to all of its neighbors: above, below, to the right, to the left, and along any diagonal. If you were to visualize this graph paper with each value mapped to the brightness of a color, you would get something that looks like clouds. White sits next to light gray, which sits next to gray, which sits next to dark gray, which sits next to black, which sits next to dark gray, etc. This is why noise was originally invented. You tweak the parameters a bit or play with color to make the resulting image look more like marble or wood or any other organic texture. Lets take a quick look at how to implement two-dimensional noise in Processing. If you wanted to color every pixel of a window randomly, you would need a nested loop, one that accessed each pixel and picked a random brightness. A random brightness To color each pixel according to the noise() function, well do exactly the same thing, only instead of calling random() well call noise() . A Perlin noise brightness This is a nice start conceptuallyit gives you a noise value for every ( x. y ) location in our two-dimensional space. The problem is that this wont have the cloudy quality we want. Jumping from pixel 200 to pixel 201 is too large of a jump through noise. Remember, when we worked with one-dimensional noise, we incremented our time variable by 0.01 each frame, not by 1 A pretty good solution to this problem is to just use different variables for the noise arguments. For example, we could increment a variable called xoff each time we move horizontally, and a yoff variable each time we move vertically through the nested loops. Example I.6: 2D Perlin noise Start xoff at 0. For every xoff, start yoff at 0.
Comments
Post a Comment