Fibonacci-Zahlen in MBASIC / GW-BASIC

  • ...weil die Fibonacci-Zahlen gestern bei Ancient Aliens erwaehnt wurden habe ich mal als Uebung dazu ein kleines Programm geschrieben ;)
    Fuer groesse Zahlen ueber 1.000.000 mussten dann die Variablen mit einer # vesrehen werden fuer Double-Precision....



  • Zeile 80


    Code
    80 IF C#>L THEN GOTO 200


    wäre was für den Herrn Dijkstra ... ;)

    und das gleich in doppelter Hinsicht ( Typfehler, Sprung )



    Fibonacci mit beliebig großen Zahlen wird dann aber schon schnell eng.

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

  • Da das mit den großen Zahlen ja evtl interessant lösbar schien, habe ich gestern mal was gebastelt, was das Thema zumindest ein wenig angeht.


    Die Berechnung erfolgt hier direkt "ohne" Berechnung. Es werden einfach die Additionen der Einzelziffern aus einem Array ausgelesen und die beiden Zahlen (die man addieren will) dazu stückweise in Einzelzeichen / Ziffern zerlegt - und zwar als String$ Operationen. Dadurch kann eine Zahl erstmal so lang sein, wie es das jeweilige BASIC für einen String$ erlaubt.




    Probe aufs Fibo ... ( äh Exempel )


    Bild1 Testlauf mit noch kleinen Zahlen


    In der Version hier wird die Auswertung der Überträge in einem IF Block gemacht. Das haben nun nicht alle BASICs , daher würde man dort stattdessen wohl einen Sprung benutzen (siehe nächstes Bild)


    Außerdem wird das C dabei auf einen Wert direkt abgefragt, was auch eleganter geht, indem man das Array der Überträge benutzt (wenns eh' schonmal da ist; C+1 bedeutet zweite Zeile auslesen (zweite Spalte ginge natürlich auch)).


    Das XB=0 vor der Konstruktion (egal ob vor dem IF Block oder vor dem Sprung) ist nötig, da es sonst nicht notwendigerweise zurückgesetzt würde, da ja nicht jede Stelle in die Übertragsauswertung "reinläuft".



    Bild2 die Version mit GOTO



    Zumindest kommt man so schonmal auf ganz schön lange Zahlen, wo man nicht mehr wüßte, wie die benannt werden ( was kommt nach der Trilliarde ... (?) ). Und die Zahlen sind schön exakt (wenn nicht noch irgendwo ein Fehler schlummert), und enden nicht nach 8 Stellen und sagen dann nur noch den 10er Exponenten dazu an (sowas macht da schnell auch ganz schön große Fehler; fast wie bei den Astronomen).



    Wer das noch weitertreiben will: Es ist eigentlich auch sehr schön möglich das von den Strings weg und hin zu einer PEEK/POKE Variante umzubauen. Dort würde man die Zahlen / Ziffern als BYTEs direkt im Speicher ablegen (quasi als ein langes Feld; kann man je nach BASIC wahrscheinlich sogar als DIM und mit Indexzugriff machen, wenn so große DIMs erlaubt sind). Dabei könnte dann eine Zahl ungefähr so groß sein wie ein Drittel des verfügbaren freien Speichers, bei 64kByte sind das schonmal ca. 21000 Stellen ... ; mit ein bißchen Trickserei kann man auch die Ergebnisziffern gleich in den Summanden S1$ fortlaufend "einarbeiten" und muß dann in jedem Durchgang beachten, daß die beiden Summanden jetzt quasi immer "wechseln" und daher das nächste Ergebnis, dann direkt in den anderen Bereich geschrieben werden muß. Damit sollten dann bei 64KByte etwas um 32000 Stellen möglich werden (vielleicht bißchen weniger, da das Programm ja auch Platz braucht).



    Wer Fehler findet, darf die gerne mitteilen ! (und nicht selbst behalten).




    Optimierung: die Zugriffe in ein zweidimensionales Array (zum Addieren) sind wahrscheinlich relativ langsam - evtl. ließen sich da auch einzelne Zeilen (eindimensional) draus machen ; die Überlauftabelle kann je nach CPU evtl als Bitmuster viel schneller ausgelesen werden (Shifts) ; die Stringverwaltung mit MID$ ist wahrscheinlich gewaltig langsam, wobei man ja eigentlich weiß, daß man immer nur genau ein Byte an einer definierten Position auslesen will ; beliebigere Größen ließen sich evtl auch durch Abspeichern/Nachladen von Teilbereichen der Zahlen (Bruchstücke; "Quanteln" mit z.B. 5000 Ziffern) erreichen ; usf



    .

  • Hier jetzt noch einer der Vorschläge. Diesmal werden die Zahlen in Form von Einzelziffern innerhalb eines Arrays abgelegt. Dabei bekommt erstmal jeder Summand sein eigenes Array. Auf das Ergebnisarray wird verzichtet - und damit auf das aufwendige Umschreiben des fertigen Ergebniswertes in eines der Summandenarrays. Stattdessen werden die Zwischenergebnisse der Ziffernadditionen direkt in das Summandenarray geschrieben, in dem die Zahl liegt, die aktuell die VORLETZTE in der Reihe ist. D.h. während der Berechnung steht in diesem Array ein lustiger Mix aus Ziffern der VORLETZTEN Zahl, die noch nicht mit Ziffern der LETZTEN Zahl addiert worden sind, und daneben bereits die ersten Ziffern aus dem Ergebniswert.

    Damit das klappt, gibt es einen Rundenzähler (RD), der immer zwischen den Werten "1" und "2" hin und her wechselt. Je nachdem wird dann in S1%() oder S2%() gelesen / geschrieben. VORLETZTE und LETZTE Zahl der Folge wechseln also in jeder Runde gewissermaßen die Plätze (bzw. das Array).



    Die schöne (und interessant krude) Variante der Addition durch Auslesen aus einer Matrix ist hier jetzt schnödem normalen Addieren der Einzelziffern gewichen. Dabei werden wieder die Überträge extra beachtet / bearbeitet.


    Die Maximallänge der Arrays kann man wohl mit FRE("-1") ermitteln - es ist aber dann möglich auch längere Werte zu definieren, ohne daß ein Out Of Memory Error o.ä. kommt ... irgendwie sehr seltsam. Deshalb muß das per Hand gemacht werden und LGMAX sinnvoll vorgegeben werden. Längen um 1024 oder 4096 Ziffern für beide Arrays sollten aber problemlos sein. Prinzipiell sollte man den Speicher auch komplett mit beiden Arrays (je 1/2 VariablenRAM) ausfüllen können.


    IF Blöcke sind wieder aufgelöst in GOTOs ; sollte also auch in uralt BASICs laufen.



    Bild1 Fibonacci Zahlen mit Einzelzifferaddition in Arrays



    Die Definition der Arrays als S1%() , S2%() legt ja nicht zwangsläufig BYTEs an. Das können also auch 16Bit Zahlen oder 32Bit sein. Auf BASICs, die DIM Variable%() nicht kennen, wird es entsprechend noch mehr ( DIM S1(256) mit je 5 BYTE o.ä. ). D.h. es geht durchaus speicher"effektiver".


    { Die vermutlich interessanteste Variante wären wohl echte BYTEs (d.h. per PEEK/POKE) und darin BCD codierte Ziffern mit je 4 Bit pro Ziffer. Dann könnten immer eine Ziffer von S1% und S2% in einem BYTE liegen. (auf dem 6502 müßte das auch superschnell per Assembler addierbar sein, da es da ja diesen Dezimalmodus gibt). }


    .



  • 256 Ziffern sind ganz schön lang ...



    Interessanterweise gibt es in der aktuellen c't gerade eine Einführung ins Programmieren und auch Artiekl zu Python / Java / JavaScript - und auch einen kleinen Abschnitt zum Thema Fibonacci Zahlen auf der Commandline. Vielleicht schön als Ergänzung zu hier mal anzusehen.


    Schönes Bild gibts auch dazu

    Programmieren lernen für Einsteiger: Python, Java oder JavaScript
    Gute Gründe, Programmieren zu lernen, gibt es viele: Jobwechsel, Informatikstudium oder ein privates Software-Projekt. Wir geben Tipps für den Einstieg.
    www.heise.de

    ( sicher auch ein schöner Hintergrund ; PicDirect )

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

  • Weil es eine nette Spielerei war, habe ich auch mal ein BASIC Progrämmchen versucht.

    Hier werden 3 BCD Ziffern in ein 16-bit Integer verpackt. 4 Ziffern wären effektiver, aber mühsam, weil die alten BASICs nur Vorzeichen behaftete Zahlen kennen und dann viele Tests und Umrechnungen nötig werden um einen Überlauf zu vermeiden.


    Das Programm läuft unter MS-DOS MBASIC, GW-BASIC und QBASIC. Es sollte eigentlich auch unter CP/M MBASIC laufen.

    Mit den kurzen ANSI Variablennamen ist es auch an Minimal BASIC Implementierungen anpassbar.


    Zeilennummern werden nur für zwei Unterprogramme zur Konvertierung Zahl <-> String (GOSUB xxxx) verwendet, bei moderneren BASICs kann man das natürlich in richtige SUBs und CALLs ändern. Wegen der Unterprogramme ist etwas Umkopiererei der Parameter erforderlich, das Ganze läuft so relativ gemächlich, ist aber viel übersichtlicher als ohne Unterprogramme.

    So läuft es aber auch auf älteren Systemen.

    Ich habe es nur so weit getrieben, dass die 80 Spalten ausgefüllt werden, prinzipiell kann man aber die Maximalzahl der Ziffern "beliebig" erhöhen.


    Interessanterweise lassen sich in GW-BASIC und QBASIC mit "PRINT ... ;" die Zeilen mit exakt 80 Zeichen ausgeben ohne dass ein Zeilenumbruch erfolgt, bei MBASIC erfolgt trotz angehängtem ";" ein Zeilenumbruch. Da müsste man dann ein Leerzeichen weniger ausgeben, damit die Zahlen rechtsbündig ohne Leerzeile ausgegeben werden.