Bei FUANAL handelt es sich um ein Programm mit einer Länge von ca. 2,5 kB (exkl. Kommentare). Es handelt sich um einen Algorithmus ZUR Analyse einer vom Benutzer innerhalb eines Programms eingegebenen Formel bzw. Funktion und sinnigerweise deren Lösung.
Sicherlich kann diese Problemlösung von vielen Programmierern benötigt werden, da das problem der direkten Eingabe von Funktionen während der Programmlaufzeit recht häufig auftritt, denn die BASIC-Funktion DEF FN ist ja nur vom Programmierer, nicht aber vom Benutzer modifizierbar.
Das Programm beinhaltet ausschließlich acht Subroutines, die ich der Reihe nach erklären möchte.
Init: Ein einmaliger Aufruf am Anfang eines Programms genügt. Es werden drei Felder dimensioniert: Res() enthält die von der Funktion definierten Variablen und Konstanten sowie die aus den diversen Rechenoperationen erhaltenen Zwischenergebnisse. Var!() und Con!() sind Flags, die bei der Eingabe einer Variablen bzw. Konstanten gesetzt werden, um eine Doppelabfrage zu vermeiden. Op1$ enthält alle Rechenoperationen mit zwei Operanden, Op2$ diejenigen mit einem Operanden.
Input: Eine vom Programmierer beliebig gestaltbare Routine, Hauptsache, die Funktion steht in Func$. Diese Variable bleibt übrigens erhalten, um sie zum Beispiel der Eingaberoutine zum Edieren übergeben zu können. Zu beachten ist das Eingabeformat: Akzeptiert werden beliebig viele Klammerebenen und alle Rechenoperationen, die in Op1$ und Op2$ enthalten sind, Zahlen sind jedoch unzulässig. Es sind je zehn Variablen und Konstanten erlaubt im Format V0 bis V9 bzw. C0 bis C9, das sollte wohl genügen. Eine gemischtquadratische Gleichung 2. Grades wird also z.B. so eingegeben: c1v1^c2+c3v1+c4. Das hat durchaus einen Sinn: Man kann die Funktion nicht nur benutzer-, sondern auch programmgesteuert mit beliebigen Werten belegen, ohne die Funktion edieren zu müssen.
Trim: Hier erfolgt die Umwandlung in Großbuchstaben, die Entfernung von Leerzeichen und die Umwandlung von V1, V2,... in 01,02,... bzw. C1, C2,... in 11, 12,..., deren Wert auf diese Weise direkt den Index im Feld Res() darstellt. V1 steht also in Res(1), V2 is Res(2), C1 in Res(11), C2 in Res(12) usw. Die restlichen Feldelemente werden die Zwischenergebnisse enthalten. Unser Beispiel sieht also so aus: 1101^12+1301+14.
Check: Es wird kontrolliert, ob eine Eingabe gemacht wurde, bzw. ob alle Klammerebenen richtig geöffnet bzw. geschlossen wurden. Die Routine kann beliebig erweitert werden, z.B. mit einer Kontrolle auf unbekannte Funktionen und Operationen.
Const: Hier werden die Konstanten eingegeben und in Res(10) bis Res(19) gespeichert. Im obigen Beispiel würde C2 also gleich 2 sein und in Res(12) stehen.
Vars: Das gleiche geschieht hier mit den Variablen. Eine Trennung der beiden Eingaberoutinen ist deswegen sinnvoll, weil man die Konstanten meistens nur einmal eingibt, die Variable(n) jedoch mehrmals. Es muß dafür also nur Vars aufgerufen werden.
Main: Hier steht der Lösungsalgorithmus, der folgendermaßen funktioniert: Gesucht wird nach der ersten geschlossenen und der dazugehörigen offenen Klammer; man erhält so eine von mehreren möglichen innersten Klammerebenen. Die For-Schleife bestimmt die höchstwertige Rechenoperation innerhalb dieser Ebene. Falls eine solche Operation gefunden wird, wird deren Position bestimmt und die beiden Operanden rechts und links des Rechensymbols aus Res() in O1 und O2 geholt. Anschließend werden diese abhängig von der Operation miteinander in R verknüpft. R wird nun in Res() ab dem Index 20 abgelegt, der String dementsprechend manipuliert (gekürzt) und der Indexpointer für Res() inkrementiert. Wird keine Operation gefunden, werden die nun unnötigen Klammern aus dem String entfernt und mit der “kleinen” Repeat-Schleife nach einer evtl. vorhandenen davorstehenden Funktion gesucht (Bsp.: SIN). Findet sich eine, wird der Operand aus Res() in O1 übertragen und das Ergebnis der Funktion in R abgelegt. Die nächsten drei Schritte sind mit den obigen drei gleich. Nun wird wieder nach einer geschlossenen Klammer gesucht, und zwar so lange, bis eben keine mehr vorhanden ist. Was übrigbleibt, ist das Ergebnis, welches in Res steht.
Herkömmliche Schreibweise: f(x,y)=sin(x+5)((4+3)/y^2)+x mit x=3, y=7
Eingabeformat: sin(v1+c1)((c2+c3)/v2^c4)+v1
Eingabe der Konstanten: c1=5, c2=4, c3=3, c4=2
Eingabe der Variablen: v1=3, v2=7
Res() sieht so aus: Res(1)=3, Res(2)=7, Res(11)=5, Res(12)=4, Res(13)=3, Res(14)=2, Rest 0
Die getrimmte Funktion lautet: (SIN(01+11)*(12+13)/02^14)+01)
Die Reduktion geht folgendermaßen vor sich:
(SIN(20)*((12+13)/02A14)+01) | In Res(20) steht res(1 )+Res(11) |
(21*((12+13)/02^14)+01) | Res(21)=SIN(Res(20)) |
(21*(22/02^14)+01) | Res(22)=Res(12)+Res(13) |
(21*(22/23)+01) | Res(23)=Res(2)^Res(14) |
(21*24+01) | Res(24)=Res(22)/Res(23) |
(25+01) | Res(25)=Res(21)*Res(24) |
(26) | Res(26)=Res(25)+Res(1) |
Das Ergebnis steht in Res(26)
Tabelle 1: Beispiel eines Rechenablaufs
Output: Das Ergebnis wird, hier nur als Beispiel, ausgedruckt. Ich denke, eine Beispiel (siehe Tabelle 1) wäre nun angebracht, stimmt’s?
Man kann den Ablauf gut verfolgen, wenn man in der Routine Main nach dem Print At-Befehl Void Inp(2) o.ä. einfügt.
Die Anzahl der Rechenoperationen sowie der Funktionen können beliebig erweitert werden. Dabei ist nur ein wenig zu beachten: Eine Rechenoperation muß in Op1$ vermerkt sein und darf nur ein Zeichen lang sein. Die Position des Zeichens in Op1$ bestimmt gleichzeitig die Priorität der Operation (von links nach rechts aufsteigend). Außerdem muß sie natürlich in der ersten Select-Case-Abfrage der Routine Main enthalten sein. Die Zahl hinter dem Case ist die Position des Zeichens in Op1$. Eine Funktion muß in Op2$ vermerkt sein, der Name darf beliebig lang sein und es gibt keine Priorität. In der zweiten Select-Case-Anweisung (nach Else) muß sie ebenfalls abgearbeitet werden. Die Zahl nach Case ist die Position, ab der der Funktionsname in Op2$ beginnt.
Beispiel: Aufnahme der Modulberechnung, dargestellt durch “&” und der Signum-Funktion Sgn():
Noch einen Hinweis: Will man eine Wertetabelle erstellen, muß man normalerweise die Routinen Vars (oder eine eigene Routine zur Variablenbelegung) und Main mehrmals durchlaufen. Dadurch wird die Formel jedesmal erneut reduziert, was jedoch Zeit kostet. Abhilfe kann man schaffen, indem man Res() um eine Dimension erweitert. Will man z.B. das Intervall von 1 bis 2,5 in Schritten von 0,5 durchlaufen und VI damit belegen, belegt man Res() vor dem Aufruf von Main: Res(1,0)=1, Res(1,1 )= 1.5, Res(1,2)=2, Res(1,3)=2.5. Eine Konstante ist für alle VI-Werte gleich, dementsprechend wird Res(1x,0) bis Res(1x,3)=Cx. Genauso oft muß man dann die beiden Select-Case-Anweisungen durchlaufen, genauer gesagt den Teil von “O1=Res(... bis Res(F1)=R”. Dies gilt in beiden Fällen, natürlich müssen alle Res()-Aufrufe angepaßt werden. Weitere Erläuterungen und die Erklärung der Variablen kann man dem Listing entnehmen.
Op1$="-+\&*/^"
Op2$="SINCOSTANLOGLNSGN"
Procedure Main, 1.Select-Case: (Case 4,5,6 um 1 erhöhen)
Case 4
R=01 Mod 02
2.select-Case: Case 15
R=Sgn(01)
Init !(c) MAXON Computer GmbH
Input
Trim
Check
Const
Vars
Main
Output
'
Procedure Init !Initialisierung
Dim Res(99),Var!(9),Con!(9) !(Zw.-)Ergebnisse
Op1$="-+\*/^" !Oper. m. 2 Operanden
Op2$="SINCOSTANLOGLN" !Oper. m. 1 Operand
Return
'
Procedure Input !Eingabe
Box 120,100,520,150v.
Text 128,120,"Bitte geben Sie die Funktion ein: "
Print At(17,9);"F=";
Form input 45 As Func$ !Func$ bleibt erhalten
Return
'
Procedure Trim !Formatieren
Fn$="("+Upper$(Func$)+")" !Nur Gr|oßbuchstaben
For I=1 To Len(Fn$) !Fn$ durchsuchen
J!=Mid$(Fn$,I|,1)=” " !hier nach Leerzeichen
Fn$=Left$(Fn$,Pred(I|))+Mid$(Fn$,I|-J!) !weg damit, falls gefunden
Add I|,J! !Zählerkorrektur
J|=Asc(Mid$(Fn$,I|)) !hier nach "C" und "V"
If J|>47 And J|<58 !"C" wird "1", "V" wird "0"
Mid$(Fn$,Pred(I|))=Chr$(48-Mid$(Fn$,Pred(I|),1)="C")
Endif
Next I|
Ln|=Len(Fn$) !Endgültige Länge
Return
'
Procedure Check !Eingabekontrolle
If Fn$<>"()" !Falls Eingabe
Clr A|
For I|=1 To Ln| !Klammern zählen
Sub A|,Mid$(Fn$,I|,1)="("-Mid$(Fn$,I|,1)=")"
Next I| !A|=0, wenn ok
If A|<>0
Alert 0," Klammerebenen|falsch gesetzt.|",1," OK ",Z|
Endif
Else !Falls keine Eingabe
Alert 0,"Keine Funktion | eingegeben.|",1," OK ",Z|
Endif
Return
'
Procedure Const !Konstanteneingabe
Locate 1,2
Arrayfill Con!(),False !Indikator zurücksetzen
For I|=1 To Ln| !Fn$ durchsuchen
J|=Asc(Mid$(Fn$,I|)) !Aktuelles Zeichen
K|=Asc(Mid$(Fn$,Pred(I|))) !Zeichen links
If J|>47 And J|<58 And K|=49 !Falls J|=Zahl und K|="1"
If Not Con!(J|-48) !und nicht eingegeben
Print " C";Chr$(J|);"=";
Input Res(J|-38) !Eingabe (in Res(10)-Res(19))
Con!(J|-48)=True !Indikator setzen
Endif
Endif
Next I|
Return
'
Procedure Vars !Variableneingabe
Print
Arrayfill Var!(), False !Indikator rücksetzen
For I|=1 To Ln| !Fn$ durchsuchen
J|=Asc(Mid$(Fn$,I|)) !Aktuelles Zeichen
K|=Asc(Mid$(Fn$,Pred(I|))) !Zeichen links
If J|>47 And J|<58 And K|=48 !Falls J|=Zahl und K|="0"
If Not Var!(J|-48) !und nicht eingegeben
Print " V";Chr$(J|);"=";
Input Res(J|-48) !Eingabe (in Res(0)-Res(9))
Var!(J|-48)=True !Indikator setzen
Endif
Endif
Next I|
Return
'
Procedure Main !Hauptroutine
F$=Fn$ !Fn$ bleibt erhalten
F|=20 !Index n. Zw.ergebnis
Repeat
Print At(17,11);F$'''''
J|=Pred(Instr(F$,")")) !Erste geschl. Klammer
K|=Succ(Rinstr(F$,"(",J|)) !Dazugehörige offene Klammer
L|=0
For I|=K| To J| !Suche höchste Oper.
L|=Max(L|,Instr(Op1$,Mid$(F$,I|,1))) !L|=Position in Op1$
Next I|
If L| !Falls vorhanden
M|=K|+Pred(Instr(Mid$(F$,K|,J|-Pred(K|)),Mid$(Op1$,L|,1))) ! hier ist sie
O1=Res(Val(Mid$(F$,M|-2,2))) !Operand links von ihr
O2=Res(Val(Mid$(F$,Succ(M|),2))) !Operand rechts
Select L| !Welche denn nu?
Case 1
R=O1-O2
Case 2
R=O1+O2
Case 3 !Zahl nach "Case" gibt
R=O1\O2 !Pos. d. Rechensymbols
Case 4 !in Op1$ wieder
R=O1*O2
Case 5
R=O1/C2
Case 6
R=O1^O2
Endselect
Res(F|)=R !Ergebnis merken
F$=Left$(F$,M|-3)+Str$(F|)+Mid$(F$,M|+3) !String kürzen
Inc F| !Index erhöhen
Else !Falls keine gefunden
F$=Left$(F$,K|-2)+Mid$(F$,K|,2)+Mid$(F$,J|+2) !Klammern eliminieren
M|=Pred(K|) !Nach vorhandener Fkt
Repeat !davor suchen
Dec M|
N|=Asc(Mid$(F$,M|))
Until N|<65 Or N|>90 !b. Zeichen<>Buchstabe
If M|<K|-2 !Falls gefunden
O1=Res(Val(Mid$(F$,Pred(K|),2))) !Operand der Funktion
Select Instr(Op2$,Mid$(F$,Succ(M|),K|-M|-2)) ! Funktion herauspicken
Case 1
R=Sin(O1)
Case 4 !Zahl nach "Case" gibt
R=Cos(O1) !Position in Op2$
Case 7 !ab der Fktname steht
R=Tan(O1)
Case 10
R=Log10(O1)
Case 13
R=Log(O1)
Endselect
Res(F|)=R !Ergebnis merken
F$=Left$(F$,M|)+Str$(F|)+Mid$(F$,Succ(K|)) !Funktion canceln
Inc F| !Index erhöhen
Endif
Endif
Until Len(F$)=2 !Bis noch einer übrig
Res=Res(Val(F$)) !Übertrag in "Res"
Return
'
Procedure Output !Ausgabe (als Bsp.)
Box 120,186,520,216
Text 128,206,"Das Ergebnis lautet: F="+Str$(Res)
~Inp(2)
Return