Symbolisches Differenzieren: Man macht’s prozedural

Das Differenzieren von Funktionen gehört heute zu den Grundfesten der Mathematik, genauer noch: der Analysis. Ableitungen gestatten z.B. die Lösung von Extremwertproblemen, das numerische Lösen von Gleichungen und eignen sich vorzüglich zur Fehlerrechnung. Der vorgestellte Algorithmus erledigt die Rechenarbeit.

Unter der Differentiation einer Funktion versteht man die Berechnung einer reellen Funktion, deren Funktionswerte genau der Steigung an jedem beliebigen Punkt einer anderen Funktion entsprechen. Um das wohl umfassendste Anwendungsgebiet der Ableitungen, nämlich die Physik, nicht auszusparen, betrachten wir einmal eine einfache Weg-Zeit-Funktion (s. Bild). Nun könnte es ja interessant sein, die Durchschnittsgeschwindigkeit zwischen zwei Punkten zu berechnen. Die Geschwindigkeit v berechnet sich als Quotient von zurückgelegtem Weg und dafür benötigter Zeit: v=(s1-s0)/(t1-t0). Das ist sie nun, die Durchschnittsgeschwindigkeit eines Körpers zwischen P1 und P2. Was aber, wenn uns die Momentangeschwindigkeit beispielsweise an P3 interessiert? Dann denken wir uns einen Punkt P4 zeitlich nahe an P3, berechnen dann die Durchschnittsgeschwindigkeit wie gehabt und lassen zu guter Letzt P4 gegen P3 streben, so daß t3 gegen t2 (und s3 gegen s2) strebt. Die Momentangeschwindigkeit ist dann definiert als Grenzwert von (s3-s2)/(t3-t2), wenn t3 gegen t2 strebt (s. Bild 1). Der Anstiegswinkel der Tangente in P3 ist der Wert der ersten Ableitung in diesem Punkt.

Das Prinzip ist jetzt klar. An einen Punkt (x|f(x)) legt man einen zweiten, unendlich nahen: (x+h|f(x+h)); h strebt gegen Null. Schließlich kann man die Tangentensteigung als Quotient aus Ordinaten- und Abszissendifferenz berechnen.

Das Ganze ist natürlich aufwendig, wenn man es für jeden Punkt macht. Es gibt aber Regeln, mit denen man Funktionen ableiten kann (Bild 2).

Oft gelingt es, einen Bewegungsablauf näherungsweise mit recht einfachen mathematischen Mitteln zu beschreiben (harmonische Schwingungen). Dann ist Differentiation natürlich sinnvoll. Wie eingangs erwähnt, ist der Anwendungsbereich enorm; deshalb sei hier auf die Literatur verwiesen ([1], [2], [3]).

Deklarativ...

So sehr die Regeln aus Bild 2 und 3 das ursprüngliche Tangentensteigungsverfahren vereinfachen, so schwierig gestaltet sich das Ableiten dennoch von Zeit zu Zeit in der Praxis. Deshalb lohnt sich die Implementation.

Deklarative Sprachen (PROLOG, LISP) sehen das Hauptziel der Programmierung in der Beschreibung einer Situation, während prozedurale Sprachen (C, PASCAL, BASIC) eine Problemlösung implementieren. Deklarative Sprachen richten ihr Augenmerk auf das WAS (obwohl es rein deklarative Sprachen eigentlich nicht gibt; in PROLOG findet eine Vermischung beider Programmierparadigmen statt). Prozedurale Programmiersprachen sind eher auf das WIE ausgerichtet, d.h., der Lösungsweg muß bis ins Detail vom Programmierer ausgetüftelt werden ([4]). Wie geeignet deklarative Sprachen für unser Problem sind, zeigt schon allein die Tatsache, daß eine Forderung an LISP vor dessen Entwicklungen war, symbolisch differenzieren zu können ([5]). Um unser Problem in PROLOG zu lösen, bedarf es keiner allzu großen Anstrengung, weil zum einen die Lösung im Standardwerk für PROLOG ([6]) bereits beschrieben ist und zum andern, weil wirklich nur eine Beschreibung der Regeln vonnöten ist (Listing 1). PROLOG erkennt nämlich Muster, also Ähnlichkeiten zu den in den Regeln beschriebenen Ausdrücken vollautomatisch; außerdem ist das Backtracking bereits implementiert, d.h. hier, daß ineinandergeschachtelte und übelst verzwiebelte Funktionen wie andere behandelt werden. Das war auch der Ausgangspunkt für die folgenden Überlegungen.

Funktion Ableitung
sqr(x) 0.5/sqr(x)
ln(x) 1/x
ig(x) 1/x*lg(e) = 0.4343/x
sin(x) cos(x)
tan(x) (cos(x))-2
arcsin(x) 1/sqr(1-x2)
arccos(x) -1/sqr(1-x2)
arctan (x) 1/(1+x2)
arccot(x) -1/(1+x2)

Bild 2: Funktionen und Ableitungen

**(i) Summenregel**

[f(x) + g(x) + h(x) + ...]' = f(x) + g'(x) + h'(x) + ...

(ii) Produktregel

[f(x) * g(x) *... * z(x)]' = f'(x) * g(x) * ... z(x) + f(x) * g'(x) * ... * z(x) + ... + f(x) * g(x) *... z'(x)

(iii) Kettenregel

[f(g(x))]' = f'(g(x)) * g'(x)

(iv) Ableitung von Potenzen

Sei eine Funktion a(x) = f(x)g(x).
Mit u = f(x) und v = g(x) gilt dann uv = a(x) ↔ v ln(u) = ln (a(x))
Dieser Ausdruck wird jetzt unter Anwendung von (ii) und (iii) differenziert:
=> v'ln(u) + v(ln(u))' = (ln(a(x)))'
Da die Ableitung von ln(x) = 1/x ist, also mit (iii): [ln(f(x))]' = f(x)*1/(f(x))
gilt: ↔ v'ln(u) + v(u'/u) = a'(x)/a(x) ↔ a'(x) = v'a(x)ln(u) + va(x)(ü/u)

(v) Ableitung von Quotienten

Da sich jeder Quotient u/v als u*v-1 schreiben läßt, können solche Terme bequem mit (iii) un (iv) abgeleitet werden. Das ist sinnvoll, weil eine Routine zur Umwandlung von Quotienten in Potenzterme einfacher und kürzer ist als eine zusätzliche Differenzierungsroutine für Quotienten, und dann noch auf die richtige Reihenfolge beim Ableiten geachtet werden muß (Multiplikation und Division sind gleichberechtigt.).

(vi) Ableitung von Konstanten und von x

C' = 0
x' = 1

Bild 3: Differenzierungsregeln

Bild 4: Ein Monster und sein Pendant
Klammer Inhalt Ableitung
§A 3+4*x §A’ = 3'+4'x+4x' = 4
§B ln§A §B’ = §A-D-§A‘
§C 1 §C' = 1' = 0
§D X §D' = x' = 1
§E -1 §E' = (-1)' = 0
§F §C*§D§E §F* = §C'(§D§E) + §C(§D§E)'
= §C'(§D§E) + §C(§E'(§D§E)ln§D+§E(§D§E)§D'*(§D-1)
§G x2*§B1.32+arccos§F §G' = (x2)'§B1.32+x2'(§B1.32)'+§F(-r(1-§F2)-0.5) = 2x§B'1.32+x21.32§B1.32*§B'B-1-§F'(1-§F2)-0.5
§H sqr§G*3 §H' = §G'0.5§G-0.5*3+3'*sqr§G = §G'0.5§G-0.5*3
§I sin§H+4*x §I' = §H'*cos§H+4'x+4x' = §H'*cos§H+4

Bild 5: Klammerinhalte und Ableitungen

... und prozedural

Jede Funktion besteht aus den Grundoperatoren +, -, *, /, ^ (binären Funktionen) und unären Funktionen wie sin(), cos(), sqr(), lg() usw. Da es für Summen, Produkte und Potenzen Ableitungsregeln gibt (Bild 3), müssen wir versuchen, die zu differenzierende Funktion auf solche Terme zu reduzieren. Wir müssen Verschachteltes entschachteln, prozedurale Sprachen erle digen das nicht wie PROLOG für uns.

Die in Bild 3 gezeigten Regeln müssen natürlich auch in PROLOG definiert werden (Listing 1).

Wir sind jetzt so weit, daß wir einfache, d.h. in keinster Weise verschachtelte Funktionen ableiten können: Zunächst wird der Termin Summanden aufgespalten, indem man nach Plussen und Minussen sucht und, davon ausgehend, nach links und rechts nach weiteren Plussen und Minussen bzw. Anfang und Ende des Funktions-Strings sucht. Dann wird jeder Summand in Faktoren aufgespalten (Quotienten haben wir bereits in Faktoren mit Potenzen ersetzt), indem man das gleiche Prinzip mit Malzeichen verwendet. Zuletzt spaltet Listing 2 die Faktoren in Basis und Exponent auf (und leitet sie auch gleich ab). Dann wird alles zusammengesetzt - nach der Ableitung.

Aufspalten in Klammerinhalte

Wie genau soll das nun funktionieren? Zunächst wird jede einzelne Klammer durch ein Paragraphenzeichen (§), gefolgt von einem Großbuchstaben (Kleinbuchstaben sind für die Funktionsbezeichner sin, cos etc.) ersetzt. Das geschieht in Listing 3.

Betrachten wir nun die Funktion aus Bild 4. Die Funktion wurde absichtlich so kompliziert gewählt, um die Vorgehensweise für alle Funktionen zu erläutern. Zunächst wird der Quotient 1/x durch (x)-1 ersetzt (s.o.). Jetzt substituieren wir jede einzelne Klammer, indem wir die erste schließende suchen - befindet sich hier nach 4x - und von dort rückwärts zur ersten öffnenden gehen - hier vor 3+4 -und diese Passage durch §A ersetzen. Danach folgt das Ersetzen von (ln§A) durch §B, dann (1) durch §C, (x) durch §D, schließlich (-1) durch §E. Weiter geht’s wiederum mit der Suche nach der ersten im String befindlichen schließenden Klammer und der korrespondierenden geöffneten. Der zu ersetzende Term ist nun (§C§D§E), er wird zu §F. Dann wird der gesamte mittlere Teil ersetzt, weil die korrespondierende Klammer nach §F vor x2 zu finden ist: (x2§B1.32+arccos§F) wird zu §G. Schließlich wird (sqr§G3) zu §H und (sin§H+4*x) zu §I. Die einzelnen Klammerinhalte werden in einem Array (originellerweise könnte man es klammer nennen) gespeichert.

Ableiten, die erste

Zur Berechnung der Ableitungen werden die Klammerinhalte zunächst in Summanden (Listing 4) und dann die Summanden in Faktoren (Anfang Listing 5) zerlegt. Die Summanden werden dann differenziert, indem man zunächst gemäß der Produktregel ableitet. Also: ersten Faktor suchen, mit Apostroph versehen und mit den restlichen Faktoren multiplizieren. Dazu dann das Produkt aus zweitem Faktor (versehen mit Apostroph) und den restlichen addieren usw. (Listing 5).

Für das Programm müssen die einzelnen Faktoren in Klammern geschrieben werden (gilt auch für Basis und Exponent), weil es sonst Chaos gibt. Denn aus (sin(x)*cos(x))' wird sonst über sin(x)'*cos(x)+cos(x)'sin(x) folgendes: sin1cos(x)*cos(1)*sin(x) und daraus dann die numerischen Näherungen. Das ist aber kein Problem, da die Routine zur Erkennung von Faktoren bereits vorhanden ist und nur wenig modifiziert werden muß.

Sollte ein Faktor ein Potenzausdruck sein, muß dieser in Basis und Exponent aufgespalten und gemäß Bild 3 abgeleitet werden. Dabei werden wieder im String die Apostrophe gesetzt (Listing 2). Die Ergebnisse der Procedures werden in einem zweiten Array namens abl gespeichert.

Ableiten, die zweite

Was bleibt zu tun? Wir müssen noch die Ableitung der unären Funktionen durchführen. Das erledigt Listing 6: Auf die Suche nach dem Argument der Funktion (das kann x, eine Konstante oder ein Klammerinhalt in Form von §... sein) folgt das Schreiben dieses Arguments (mit Apostroph zur Kenntlichmachung der Ableitung) als Faktor vor die Ableitung der Funktion (aus f§A, mit f als irgendeinem Funktionsbezeichner, wird also §A'*g§A, wobei g der Funktionsbezeichner der Ableitung ist). Das ist die Implementation der Kettenregel. Die Regeln für das Ableiten von unären Funktionen (Auswahl) sind in Bild 2 abgedruckt.

Ein Beispiel: In der Klammer §B steht ln§A. Das Argument ist §A, dessen Ableitung (es ist die innere!) §A'; die Funktion ist ln, die Ableitung davon ist der Kehrwert des Arguments ((ln(x))'=1/x). Das ist die äußere Ableitung. Daraus basteln wir nun den differenzierten Term: §A'(1/§A) bzw. §A'§A-1. Die Ableitungen der Klammern sind aus Bild 5 ersichtlich.

Zusammensetzen

Von hinten beginnend, wird jetzt eingesetzt (Listing 7). In §I' werden §H' und §H ersetzt; daraus entsteht ein Term mit §G und §G'. Hier wird genauso verfahren. Im Listing wird allerdings solange ersetzt, bis sich kein Klammermakro mehr im String befindet, weil bei der anfänglichen Substitution der Klammern durch Paragraphenmakros darauf geachtet wurde, daß derselbe Klammerinhalt nicht zwei verschiedene Makros erhält. Wir sind ja sparsam!

Jetzt müssen noch x' und Konstanten plus Apostroph durch 1 bzw. 0 ersetzt werden - und der abgeleitete Term ist fertig. Allerdings ist er noch roh und häßlich und bedarf einer Nachbearbeitung - doch das Kürzen und Zusammenfassen von Termen ist eine andere Geschichte!

Der Trick

Das einzig Trickreiche am Algorithmus wird durch die Mathematik ermöglicht: f(g(h(x))) kann man auch als f(§u) schreiben, wenn §u=g(h(x)) ist. Und die Ableitung von f(§u) ist gleich §u’*f (§u). g(h(x)) läßt sich als g(§t) schreiben, die Ableitung ist §t’*g’(§t) usw. Sukzessives Einsetzen ergibt schließlich das Gewünschte. Damit haben wir das Backtracking von PROLOG wirkungsvoll nachgeahmt. In der Regel differenziert man solch verschachtelte Funktionen nach bestimmten Schemata, das Ergebnis ist aber das gleiche (nachrechnen schadet nicht!)

Die Listings Nicht erwähnt ist bisher Listing 8. Listing 8 stellt das Hauptprogramm bzw. dessen Gerüst dar. Die Aufrufe an andere Routinen sind im Programm belassen worden (mit ’ als Indikator für Bemerkung). Diese Routinen sind entweder zu umfangreich (PROCEDURE verkuerzen) oder selbstverständlich (PROCEDURE spc_raus). Eine Besonderheit: vor Programmaufruf werden die Funktionsbezeichner durch einbuchstabige Token ersetzt. Das reduziert die Zeit für Lesezugriffe ca. um den Faktor 2. Die Funktionsnamen sind aber angegeben, wo es nötig schien.

Die PROCEDURE spalt_fak_abl, das Kernstück des Algorithmus, mag unübersichtlich und kompliziert erscheinen. Das ist sie auch (vor allem mit den vielen Leerzeichen und den massig erzeugten Pluszeichen im Ableitungs-String), diese Version ist aber eine der zeitsparendsten. Es geht nämlich etwas fixer, später überflüssige Pluszeichen zu eliminieren als in der Ableitungsroutine ständig darauf achten zu müssen, ob ein Pluszeichen jetzt gesetzt werden darf oder nicht. Sollte dem Autor Unglaubwürdigkeit attestiert werden wollen, so mag man eine eigene Routine schreiben.

Für die Routinen müssen Funktionsnamen durch Token ersetzt sein, der Term muß syntaktisch korrekt sein und darf keine Leerzeichen enthalten.

Fazit und Ausblick

Die Implementation des Algorithmus inklusive Vereinfachung, Formel-Parser, Kommentaren umfaßt in BASIC ca. 2500 Zeilen, in PASCAL etwas mehr. Die Implementation des Algorithmus allein macht etwa 500 Zeilen aus.

Da stellt sich natürlich eine Frage: warum der Aufwand, wenn es in PROLOG viel kürzer geht? Nun, es ist sicherlich nicht immer einfach und sinnvoll, PROLOG-Routinen in eigene Programme zu übernehmen. Außerdem ist der Algorithmus an sich interessant - und PROLOG muß es ja auch irgendwie machen!

Die Routine kann so nicht bestehen bleiben, die erwähnten Vereinfachungsroutinen müssen schon etwas Verschönerungsarbeit betreiben (Pluszeichen entfernen, zusammenfassen, kürzen und vor allem: überflüssige Klammem eliminieren). Ein Beispiel liefert Bild 4. Dann ist auch der Schritt zur Umformung von Gleichungen nicht mehr weit - z.B. die Implementierung der Logarithmusgesetze oder das symbolisches Lösen von Gleichungen.

Hier wurde das Beispiel expliziter Funktionen gewählt. Der Algorithmus funktioniert aber entsprechend für parametrisierte Funktionsdarstellungen (2 Gleichungen, 2 Aufrufe): x ist dann der Parameter; auf Wunsch kann man im Programm jedes ,x‘ durch das für Parameter übliche ,t‘ ersetzen.

Das Listing ist in GFA-BASIC 2.02 erstellt; wer Interesse an einer PC-Version in BORLAND-PASCAL oder sonstige Fragen hat, möge sich beim Autor melden.

/* Listing 1
   Symbolisches Differenzieren in PROLOG 
   nach Clocksin/Mellish 
   ~: Negation
   ^: Exponentiation; manche Interpreter 
      gestatten **
   Aufruf: d(ln(x)*sin(x^2+3),x,K). 
   modifiziert von Alex Pretschner	     */

d(X,X,1) :- 1.          /* x'=1         */
d(C,X,0) :- atomic(C).  /* Konstante    */
d(~U,X,~A) :- d(U,X,A). /* Negation     */
/* Summenregel */
d(U+V,X,A+B) :- d(U,X,A), d(U.X,B).
d(U-V,X,A-B) :- d(U,X,A), d(U,X,B).
/* konst. Paktor */
d(C*U,X,C*A) :- atomic(C), C\= X, d(U,X,A), !. 
/* Produktregel */
d(U*V,X,B*V+A*U) :- d(U,X,B), d(V,X,A). 
d(U/v,X,A) :- d(U*VA(~1),X,A). /* Quotient */
/* Funktionsableitungen */ 
d(ln(U),X,A*U^(~1)) :- d(U,X,A). 
d(sin(U),X,A*cos(U)) :- d(U,X,A). /* usw. */
/* Potenzen */
d(U^V,X,B*U^V*ln(U)+V*U^V*A*U^(~1)) :- d(U,X,A), d(V,X,B).
PROCEDURE pot_abl 
    ' FUNKTION: Potenzausdrücke ableiten 
    ' rein: ab$(i%)
    '
    LOCAL a%,b%,hlp%,k%,kla%,t$,links$,rechts$,abl$
    '
    b%=0 
    hlp%=0 
    abl$=""
    ab$(i%)=" "+ab$(i%)+" " !Position ganz links und ganz rechts 
    a%=INSTR(ab$(i%),"^") 
    k%=a%
    '
    WHILE a% 
        kla%=0 
        REPEAT
            INC a%      ! rechtes Ende des Potenztermes 
            t$=MID$(ab$(i%),&%,1)
            ' Klammern müssen gezählt werden, weil neue dazugekommen sein konnten 
            IF t$="(" 
                INC kla%
            ENDIF 
            IF t$=")"
                DEC kla%
            ENDIF
        UNTIL ((t$="'" OR t$="+" OR t$="-" OR t$="*" or t$=")") AND kla%=0) OR a%=LEN(ab$(i%))
        IF MID$(ab$(i%),&%,1)="'"   !Potenz ableiten 
            hlp%=a%
            REPEAT 
                DEC hlp%
                hlp$=MID$(ab$(i%),hlp%,1)
            UNTIL hlp$=" " OR hlp$="x" OR hlp$="§"
            '
            hlp%=a%
            b%=a%                   !Position merken
            '
            REPEAT 
                DEC a%
            UNTIL MID$(ab$(i%),a%,1)="^"
            ' Exponent
            rechts$=MID$(ab$(i%),a%+1,b%-a%-1) 
            b%=a%
            REPEAT 
                DEC a%
                hlp$=MID$(ab$(i%),a%,1)
            UNTIL hlp$=" " OR hlp$="+" OR hlp$="-" OR hlp$="(" 
            links$=MID$(ab$(i%),a%+1,b%-a%-1) !Basis
            '
            ' Potenz ableiten 
            ' Exponent nicht nur Zahl
            IF INSTR (rechtst,"x") OR INSTR(rechts$,"§") 
                ' Basis nicht nur Zahl 
                IF INSTR(links$, "x") OR INSTR(links$,"§")
                    abl$=links$+" ^ "+rechts$+" * "+rechts$+"'" + " * m ( "+links$+" ) + "+rechts$+" * "+links$+"'" + " * "+ links$+" ^ ( "+rechts$+" - 1 ) "
                ELSE                    !Basis nur Zahl
                    abl$=links$+" ^ "+rechts$+" * "+rechts$+"'"+"* m ( "+links$+" )"
                ENDIF
            ELSE                        !Exponent nur Zahl
                abl$=links$+"' * ( "+rechts$+" ) "+" * ( "+links$+" ) ^ "+" ( "+rechts$+" - 1 ) "
            ENDIF
            '
            ab$(i%)=LEFT$(ab$(i%),a%)+" ( "+abl$+" ) "+RIGHT$(ab$(i%),LEN(ab$(i%))-hlp%)+" "
            ADD a%,2+LEN(abl$)
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"^")
    WEND
    ' raus: ab$(i%)
RETURN
PROCEDURE klammerbaum 
    ' rein: a$
    LOCAL a%,z$,z%,b%
    ' Klammern abspeichern und Baum erzeugen 
    ' Klammerinhalte in Array kl$().
    ' Wenn eine Klammer abgespeichert wurde, wird sie durch zwei Zeichen ersetzt:
    ' § als Indikator für eine Klammer und einem Index (verschiedene ASCIIs).
    ' klammer% ist die Anzahl der Klammern.
    '
    klammerl%=-1
    REPEAT
        a%=INSTR(a$,")")
        IF a% 
            b%=a%
            REPEAT 
                DEC b%
            UNTIL MID$(a$,b%,1)="("
            '
            ' vermeiden, daß zweimal die gleiche Klammer verschiedene Namen kriegt 
            z$=MID$(a$,b%+1,a%-b%-1) 
            z%=-1 
            REPEAT 
                INC z%
            UNTIL z$=kl$(z%) OR z%=klammer%+1
            '
            IF z%=klammer%+1 
                INC klammer% 
                @overflow 
                kl$(klammer%)=z$
            ENDIF
            '
            a$=LEFT$(a$,b%-1)+"§"+CHR$(95+z%)+MID$(a$,a%+1,LEN(a$)-a%+b%+1)
        ENDIF 
    UNTIL a%=0
    ' raus: a$,klammer%, array kl$
RETURN

PROCEDURE spalt_sum(b$)
    ' rein: übergeben 
    ' Klammerinhalt in Summanden zerlegen 
    LOCAL a%, st%, issjpos^
    '
    ' Extinktion der Felder notwendig, weil Mehrfachaufruf 
    ERASE su$()         !Summanden
    ERASE su!()         !Vorzeichen: 0:+; -1:-
    '
    ' Plusse und Minusse zählen; daraus wird dann die Anzahl der Summanden berechnet 
    ' Anzahl der Plusse und Minusse;
    ' 1 Plus : 2 Summanden 
    i%=0
    a%=INSTR(b$," + ")
    WHILE a%
        INC i%
        a%=INSTR(a%+1,b$,"+")
    WEND
    a%=INSTR(2,b$,"-")
    WHILE a%
        INC i%
        a%=INSTR(a%+1,b$,"-")
    WEND
    '
    ' Felder kreieren
    DIM su$(i%),su!(i%)     !Summand und sein SGN
    '
    pos%=0                  !Position im String
    anfang%=1               !Anfangsposition des Summanden 
    su%=-1                  !Summandenzähler
    b$=b$+"+"               !für letzten Summanden
    st%=1                   !1.Zeichen 1.Summand
    '
    REPEAT
        INC pos% !jedes Zeichen im String
        IF MID$(b$,pos%,1)="+" OR MID$(b$,pos%,1)="-" !+ oder - gefunden 
            IF pos%=1           !1. Zeichen '-'
                anfang%=2       !ab zweiter Stelle suchen 
            ELSE
                INC su%         !Anzahl der Summanden
                su$(su%)=MID$(b$,anfangs,pos%-anfang%)
                !Summanden speichern 
                su!(su%)=MID$(b$,st%,1)="-"
                !wenn minus, dann su!()=true 
                anfang%=pos%+1 
                !neuer Start Summandensuche 
                st%=pos% 
                !1.Zeichen neuer Summand 
            ENDIF 
        ENDIF
    UNTIL pos%=LEN(b$)      !Stringende
    ' raus: array su$,su!, su%
RETURN
PROCEDURE spalt_fak_abl
    ' rein; array su$,su!, su%
    ' FUNKTION: jeden Summanden in Faktoren zerlegen und einzeln ableiten 
    LOCAL i%,g$,anfang%,pos%,v$
    '
    FOR i%=0 TO su%
        '
        ' Term in Faktoren zerlegen und ableiten 
        g$=su$(i%) 
        v$=""
        IF INSTR(g$,"*")    !mehr als ein Faktor 
            g$=g$+"*1"      !für den letzten Faktor
            pos%=0 
            anfang%=1 
            REPEAT 
                INC pos%
                IF MID$(g$,pos%,1)="*""
                    IF INSTR(MID$(g$,anfangs,pos%-anfang%),"x") OR INSTR(MID$(g$,anfang%,pos%-anfang%),"§")
                        v$=v$+" + "+MID$(g$,anfangs,pos%-anfang%)+"' * "+LEFT$(g$,anfang%-1)+RIGHT$(g$,LEN(g$)-pos%)
                    ENDIF
                    anfang%=pos%+1 
                ENDIF 
            UNTIL pos%=LEN(g$)
        ELSE
            IF INSTR(g$,"x") OR INSTR(g$,"§")
                v$=g$+"'"
            ELSE
                v$="0"      !einzelner Summand
            ENDIF 
        ENDIF
        '
        '
        IF su!(i%)=0        !PLUS
            k$=k$+"+"+v$    !v$ ist der abgel. Term
        ELSE                !MINUS
            k$=k$+"-("+v$+")"
        ENDIF 
    NEXT i%
    ' raus: k$
RETURN
' Definitionen für Ableitungen 
' Teil bis zum Abzuleitenden 
DEFFN li$=LEFT$(ab$(i%),a%-1)
' Teil ab dem Abzuleitenden
DEFFN re$=RIGHT$(ab$(i%),LEN(ab$(i%))-a%-3) 
' innerer Texl mit Apostroph 
DEFFN in1$=MID$(ab$(i%),a%+1,3)
DEFFN in2$=MID$(ab$(i%),a%+1,2)     !und ohne
DEFFN se=MID$(ab$(i%),a%+3,1)="'"   !Ableitung?
'
PROCEDURE funkt_abl
    '
    ' FUNKTION: Funktionen ableiten 
    ' rein: ab$(i%)
    LOCAL a%
    '
    a%=INSTR(ab$(i%),"n§") !arcsin 
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*(1-"+@in2$+"^2)^(-0.5)"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"n§")
    WEND
    a%=INSTR(ab$(i%),"o§") !arccos 
    WHILE a%
        IF @se
            ab$(i%)=@li$+"-"+@in1$+"*(1-"+@in2$+"^2)^(-0.5)"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"o§")
    WEND
    a%=INSTR(ab$(i%),"p§") !arctan 
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*(1+"+@in2$+"^2)^(-1)"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"p§")
    WEND
    '
    a%=INSTR(ab$(i%),"h§") !sin; i:cos 
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*(i"+@in2$+")"+@re$ 
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"h§"")
    WEND
    '
    a%=INSTR(ab$(i%),"j§") !tan 
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*(i("+@in2$+")^(-2))"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"j§")
    WEND
    a%=INSTR(ab$(i%),"g§") !sqr
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*0.5*("+@in2$+"^(-0.5))"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"g§")
    WEND
    a%=INSTR(ab$(i%),"m§") ! ln 
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*("+@in2$+"^(-1))"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"m§")
    WEND
    a%=INSTR(ab$(i%),"l§") !lg 
    WHILE a%
        IF @se
            ab$(i%)=@li$+@in1$+"*(("+@in2$+")^(-1)*(l(e)))"+@re$
        ENDIF
        a%=INSTR(a%+1,ab$(i%),"l§")
    WEND
    ' raus; ab$(i%)
RETURN
PROCEDURE resubst 
    ' rein: abl$
    ' verkürzte Klammern wieder umwandeln 
    LOCAL i%,j%,k%,old$
    REPEAT
        old$=abl$
        FOR i%=klammer% DOWNTO 0 
            j%=i%+95
            k%=INSTR(abl$,"§"+CHR$(j%))
            WHILE k%
                abl$=LEFT$(abl$,k%-1)+"("+kl$(i%)+")"+RIGHT$(abl$,LEN(abl$)-k%-1)
                k%=INSTR(k%+7+LEN(kl$(i%)),abl$,"§"+CHR$(j%))
            WEND 
        NEXT i%
    UNTIL old$=abl$
    ' raus: abl$
RETURN
PROCEDURE diff(a$)
    ' übergeben wird der Term in Tokenform mit Malzeichen, allen nötigen Klammern 
    ' und den umgebenden Klammern. Divisionen sind durch (...)^(-1) dargestellt.
    ' Zum Differenzieren müssen eingeklammert sein: der gesamte Term, Faktoren 
    ' (wenn sie zusammengesetzt sind), Exponenten und Basen.
    ' Funktionen: +-*^ arc(sin cos tan cot)
    ' arc (sec csc) sqr ln lg e x 
    ' arc(sinh cosh tanh coth)
    '
    a$="("+a$+")"           !für Klammerbaum
    ' @einklammern          !s. Text
    @klammerbaum
    '
    ' Klammern einzeln ableiten 
    ERASE ab$()
    DIM ab$(klammer%)       !klammer% kommt aus
    ' Proc klammerbaum; Anzahl der Klammern 
    FOR i%=0 TO klammer%
        k$=""
        ' In Summanden aufspalten 
        @spalt_sum(kl$(i%))
        @spalt_fak_abl
        'Summanden in Faktoren aufspalten, Summanden 
        ' ableiten und zusammensetzen (Produktregel)
        ' Leerzeichen eliminieren
        ' @spc_raus(k$) !kommt aus P spalt_fak_abl 
        ab$(i%)=z$
        @funkt_abl      !unäre Funktionen
        '        ableiten mit Kettenregel 
        @pot_abl        !Potenzen ableiten
    NEXT i%
    '
    ' abgelittene Klammern resubstituieren 
    abl$=ab$(klammer%)
    FOR i%=klammer% DOWNTO 0
        k%=INSTR(abl$,"§"+CHR$(i%+95)+"'")
        WHILE k%
            abl$=LEFT$(abl$,k%-1)+"  ("+ab$(i%)+"  )"+RIGHTS(abl$,LEN(abl$)-k%-2) 
            k%=INSTR(k%+7+LEN(ab$(i%)),abl$,"§"+CHR$(i%+95)+"'")
        WEND 
    NEXT i%
    ' 'normale Klammern resubstituieren 
    @resubst
    '
    ' @kurz             !vereinfachen; rein: abl$
    zz$=abl$            !Funktionsnamen sind noch
    ' durch Token ersetzt
    '
    ' Abkürzungen durch richtige Namen ersetzen
    '
    ' äußere Klammern entfernen (v. klammerbaum) 
    abl$=MID$(abl$,2,LEN(abl$)-2)
    PRINT AT(10,23);"... Taste ..."
    VOID INP(2)
    CLS
    abl$=zz$
    ' raus: derda$ (Klartext), abl$ (Token, durch ../.. ersetzt.
RETURN

Alexander Pretschner
Aus: ST-Computer 05 / 1994, Seite 80

Links

Copyright-Bestimmungen: siehe Über diese Seite