Tabellen = Rechnen mit Speed, ohne zu rechnen ("Programmieren" lernen)

  • Manchmal ist es sehr hilfreich, rechnen zu können (auch ganz allgemein). Es gibt aber auch den Fall, daß es VIEL besser ist, sich zu überlegen, ob man Dinge nun wirklich mit der CPU berechnen muß, oder ob es evtl. anders besser geht, insbesondere schneller.

    Denn: Ergebnisse nachschauen, ist oft nicht so aufwendig wie Neuberechnung.


    Die Lösung für schnelles Nachsehen sind Tabellen.


    Dafür belegt man einen Speicherbereich mit den Ergebnissen für vorhersagbare oder erwartbare Berechnungen. Immer dann, wenn eine solche Berechung notwendig wäre, sieht man in der Tabelle nach. Ein schönes "Verfahren" dafür ist, einen sogenannten Pointer zu benutzen. Der funktioniert i.P. ähnlich wie der Stackpointer (SP) beim Stapelspeicher, allerdings wird mit ihm nicht eine freie Speicherstellen angezeigt, sondern das gewünschte Ergebnis in der Tabelle.

    Dieses kann dann geladen und verwendet werden.


    (Pointer und Ergebnis müssen dabei natürlich irgendwie "sinnvoll" verknüpft sein; man kann sich das so vorstellen, wie bei einer Steuerkurve, der Pointer ist der X-Wert, den man auf eine Position setzt und dann das Ergebnis aus der Kurvengrafik abliest.)


    Man ist dabei auch nicht auf ein Ergebnis pro Poitnerposition begrenzt, sondern kann mit mehreren parallel geführten Tabellen auch verschiedene Ergebnisse zu einer Pointerposition auslesen. Etwa bei zwei Winkeltabellen, bei denen man einen Winkel in Grad vorgibt (Pointer) und das Ergebnis in Radiant oder Neugrad ablesen kann.


    Benutzbar sind Tabellen für alles mögliche: einfaches Mappen von Werten (also Wert1 wird zu Wert2), mathematische Tabellen (insbesondere solche für Logarithmen oder Winkelfunktionen), direktes Abspeichern von Werten in definierten Reihenfolgen, aber auch Abfolgen von Subroutinen oder Anordnungen von Betriebssystemfunktionen sind denkbar.


    Vorteil: Schnell ! ; einfach in der Benutzung ; können als Indirektion benutzt werden ; können verknüpft werden

    Nachteil: Tabellen benötigen Speicherplatz, teils sogar viel davon - auf kleinen RAMs u.U. soviel (relativ), daß sie nicht praktikabel sind (ZX81 o.ä.)

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502=


    Farbwerte berechnen, und dadurch Helligkeiten ändern, mit ADC#$10 gab es schonmal als Demo. Gerade bei solchen immer wiederkehrenden gleichen Ergenissen, macht eine Tabelle extrem viel Sinn.


    Dazu muß man sie natürlich anlegen. Hier steht die Tabelle bei $5200 bis $520F, d.h. 16 Werte.

    Es handelt sich einfach um die möglichen Helligkeiten von Rot, und damit es schön aussieht, sind "heller werden" und "dunkler werden" hintereinander in der Tabelle vorhanden.



    SUBWAIT2 wird benötigt bei $6000


    Der "Pointer" wird im X-Register geführt und läuft von Position 15 bis zu Position 1. Der zugehörige "ermittelte" Farbwert wird ausgelesen und auf alle Bildschirmpositionen geschrieben.


    Wer mag, erweitert die Tabelle um nochmal 16 Positionen und schreibt dort z.B. eine andere Farbe hinein, z.B. $03 statt $02 (alle hinteren 2en durch 3en ersetzen). Das X-Register sollte dann natürlich bei #$1F beginnen. Die $01 auf Position $520F würde dann auch erscheinen.


    (Eine schöner Effekt ergibt sich auch, wenn die Rotwerte versetzt in die fünf Farbspeicherpositionen geschrieben werden - wozu man das Programm nur ein klein wenig umbauen muß.)


    [Anmerkung: Das wäre jetzt auch eine potentielle Anwendung für NOPs ! Je nachdem wie die Zeitaufwände zwischen dem Teil in der Schleife und den Berechnungen außerhalb verteilt sind, kann es mit NOPs möglich sein, den jeweils schnelleren Teil so zu verzögern, daß "weiche" und "gleichlange" Farbwechsel beim Rücksetzen des Pointers auf die Ausgangsposition erreicht werden.]

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502=


    Benötigt wird ab $5000 zsätzlich der erste Teil der Routine, die jedes Bildschirmzeichen als Akkuinhalt an die Subroutine schickt (in Bild 2 nur angedeutet dargestellt). Ebenso wird SUBWAIT2 ($6000) wieder benötigt.


    Es handelt sich um eine Variante der ROT13 Umwandlung - diesmal aber per Tabelle.

    Bei $5050 ist der Startpunkt, da dort zunächst die Tabelle selbst angelegt wird.

    Sobald die Tabelle einmal im Speicher steht, kann man alle weiteren Programmläufe auch mit G $5000 starten.

    (es ist auch möglich die per Hand einzugeben - oder abzuändern)


    Das Bild1 zeigt das Erstellen der Tabelle und die Tabelle selbst - in diesem Fall sind es sogar zwei. Ab $5200 stehen die Buchstabenwerte für "A" bis "Z" in normaler Reihenfolge (fortlaufende Werte von $01 (das A) bis $1A (das Z)).

    Die Tabelle ab $5300 zeigt die Werte für die mit ROT13 "rotierten" Buchstaben, also den Wert, mit dem der jeweilige Buchstabe ersetzt wird, wenn er auf der gleichen Position in der Tabelle steht.




    Der eigentliche Tausch findet für jedes Zeichen ab $5100 statt. Es wird dort die Tabelle1 ($5200) von hinten nach vorn durchlaufen, Pointer ist das Y-Register (das XR wird bereits bei $5000 benutzt und ist darum nicht "frei").

    Wird eine Übereinstimmung des aktuellen Zeichens mit einem Wert in der Tabelle1 gefunden, wird dieser durch den korrespondierenden Wert aus Tabelle2 ($5300) ersetzt.

    Wird keine Übereinstimmung gefunden, bleibt der Originalwert unverändert.


    Soetwas nennt man: Mappen bzw. Ummappen.



    Frage: Wie vermeidet man, daß auch Zeichen, die gar nicht in den Tabellen gefunden werden können, die komplette Subroutine, und v.a. alle Tabellenpositionen, durchlaufen ?


    Frage: Ist es damit möglich jede beliebige "buchstabenaustauschende" Verschlüsselung gleichzeitig zu verschlüsseln, wie auch anschließend - in einem zweiten Durchlauf - wieder zu entschlüsseln ?


    Frage: Wäre es besser - für deutsche Texte - die Tabelle von vorn nach hinten oder eher andersherum (wie hier gezeigt) zu durchlaufen ? Gäbe es eine andere, evtl. bessere, Variante ?

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502=


    Das Gute an Tabellen ist aber auch, daß man sie gar nicht kompliziert in Maschinencode zusammenbauen muß. Oft ist es einfacher sie "woanders" herzunehmen. Ein allfällig schönes Beispiel sind Sinustabellen. Dort schreibt man zu einem vorgegebenen Winkel den zugehörigen Sinuswert hinein. Optional kann dieser auch bereits auf irgendwelche Bildschirmgrößen o.ä. angepaßt sein.


    Im Bsp. wird nun eine Sinustabelle für die 40 Zeichen des Textbildschirms angelegt - ein Sinuswert für jede Spalte. Dabei wird darauf geachtet, daß die Werte wieder am Startwert ankommen, d.h. zumindest eine komplette Schwingung absolviert wird (oder Vielfache davon).

    Die Amplitude wird nicht bei -1 bis +1 belassen, sondern bereits in der Tabelle an die Ausgabe angepaßt. Hier sind das 6 Bildschirmzeilen Höhe, daher wird der vom Sinus abgedeckte Bereich auf 0 bis 5 umgerechnet.



    Dazu verläßt man den Monitor mit X+Return und gibt das BASIC Programm ein, startet es mit RUN und kann dann die fertige Tabelle bei $5200 ansehen. Bei $5100 liegt noch eine weitere, die aber eigentlich nicht benutzt wird, es aber vielleicht anschaulicher macht.


    Was ist der Unterschied ? Bei Tabelle $5200 wird der auf die Höhe von 6 Zeilen bereits eingegrenzte Wert noch mit 40 multipliziert. Das ist genau die Zeilenlänge und man erhält so einen Wert, der für jede Position einen Index ergibt, den man z.B. auf den Bildschimspeicheranfang addieren kann und bei der richtigen Höhe, wenn auch erstmal nur am Anfang einer Zeile, landet. Der Sinuswert wandert also ganz links am Bildschirm hoch und runter.




    SUBWAIT2 wird wieder ab $6000 benötigt.


    Als nötige Zusatzroutinen gibt es bei $5040 eine Kopierroutine, die, wieder mal, den Text der ersten Bildschirmzeile in einen Puffer (bei $5300) kopiert. Mit G $5040 wird das Program erstmalig gestartet, für weitere Starts reicht dann auch G $5000.


    Bei $5050 wird, in einer Subroutine, in die obersten Bildschirmzeilen 200x ein SPACE geschrieben. Das ist also einfach nur eine Löschroutine. Wer mag kann, da auch alle 6 Zeilen löschen, indem das X-Register mit #$F0 geladen wird.



    Das eigentliche Beispiel nutzt nun die Sinustabelle von $5200. Der Befehl bei $5008 liest aus dieser einen Wert ein.

    Es ist ein bißchen "quirky", aber es geschieht folgendes: Es werden zwei Indexwerte (Inidizes) benutzt; das X-Register sagt an, welches von den 40 Zeichen der Zeile gerade bearbeitet wird, das Y-Register läuft parallel dazu die Sinustabelle ab und holt dort aufeinanderfolgende Werte ab. Das diese Werte aus der Sinustabelle ja aber bereits die "richtigen" Höhen/Zeilen ab Startposition des Bildschirms enthalten, wird darauf nun nur noch der Wert des X-Registers addiert und man gelangt somit an die richtige Position für das aktuelle Zeichen ($5006-$500B). Der errechnete Wert kann einfach als Index in den Bildschirmspeicher benutzt werden und zeigt die Stelle, an die geplottet wird ($500F).


    Ein bißchen unübersichtlich wird es dadurch, daß das Y-Register einmal als Index in die Sinustabelle fungiert und kurz darauf als Index in den Bildschirmspeicher zum Plotten, das Ganze zudem unabhängig vom X-Register. Damit das funktioniert wird es in $D0 zwischengespeichert und der Index in die Sinustabelle am Schluß wieder im Y-Register hergestellt. Und weil es sonst zu langweilig wäre, wird dieser Wert nun auch noch verändert, so daß die Startposition in der Sinustabelle nach jeder Runde um -1 nach vorn/unten in dieser Tabelle läuft.


    Kurz (und andersherum gesagt): Der Index in der Sinustabelle ändert regelmäßig seine Position und beginnend mit diesem Wert wird eine Textzeile aufgebaut (40 Zeichen), wobei die Einzelzeichen, durch das Addieren mit der Höheninformation aus der Tabelle, nun aber zusätzlich eine "flexible" Höhe erhalten.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • =6502=


    Und nochmal: Der Scroller.

    Da war ja noch die eine Variante (b.) es zu ändern offen.


    Ein ganz einfache Lösung für einen Scroller ist, den Text schlicht zweimal hintereinander im Speicher abzulegen. Und anschließend "durchfährt" man mit einem Pointer diesen Speicher vom Beginn bis zur Hälfte und alles, was hinter dem Pointer liegt und auf eine Zeile paßt, wird kopiert.



    SUBWAIT2 wird ab $6000 benötigt.


    $5000-$500C ist die Vorbereitung. Einlesen der ersten Zeile und zweimal hintereinander in einen Puffer ablegen.

    Ab $500E kommt die Kopierroutine, wieder mit zwei unabhängigen Pointern, YR für den Textpuffer und XR für die Bildschirmzeile.


    Frage: Wie kann man hier die Geschwindigkeit des Scrollers verdoppeln ?


    Man sieht v.a. auch gut, daß keinerlei Umkopieren von Einzelzeichen an den Rändern oder Retten von Zeichen aus dem Bildschirm mehr stattfindet.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

  • Warum ist das Verdoppeln eines Textes ein relevantes Beispiel für Tabellen ?


    Es ist eine ziemlich einfache Möglichkeit deren Benutzbarkeit erheblich angenehmer zu machen. Man muß dann nämlich die äußeren Grenzen der Tabelle nicht mehr für das Auslesen beachten, da man sie nicht mehr "überqueren" muß.

    Wenn man wie mit einem "Fenster" (also einem eingeschränkten Bereich an X-Werten) auf so eine Tabelle schaut und dieser genausobreit wie die Tabelle selbst ist, kann man nach einfachem Verdoppeln der Tabelle jede beliebige Anordnung der Werte, inkl. den sonst evtl. nötigen Überschreitungen an den Rändern, der Tabelle "ansehen", indem man den Index bewegt.


    Schön veranschaulichen kann man sich das evtl. bei Sinustabellen.


    Wenn die Tabelle die Werte für die Winkel von 0 bis 359 Grad umfasst, kann man in einem Stück durch einfaches Pointer-erhöhen nur eine einzige "vollständige" Sinuskurve daraus ablesen. Nämlich die, die bei 0 Grad beginnt.

    Bei Verdoppelung der Tabelle kann der Startpunkt nun jeden Winkel annehmen und trotzdem können noch alle Werte für eine vollständige Schwingung ausgelesen werden.



    Dort gibt es dann auch auch noch eine weiter interessante Variante der "Verlängerung". Verlängert man eine normale (0-359 Grad) Tabelle mit Sinuswerten um 90 Grad (also 0-449 Grad), kann man hieraus gleichzeitig - wenn man nämlich mit dem Pointer bei 90 Grad beginnt - die Cosinuswerte in ihrer Abfolge einfach durch Pointerversatz auslesen.

    Es ist dann quasi eine (einfache) Sinus/Cosinus Tabelle.


    Frage: Wie lang (in Grad) müßte eine solche sein, wenn man für beide(!) Winkelfunktionen die oben beschriebene Verdoppelung und damit das bequemere Auslesen einer kompletten Schwingung für jeden Anfangswinkel haben möchte ?

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()