home *** CD-ROM | disk | FTP | other *** search
/ Turbo Toolbox / Turbo_Toolbox.iso / spezial / 04 / calcprog.doc < prev    next >
Encoding:
Text File  |  1988-10-14  |  30.3 KB  |  804 lines

  1.  
  2. TOOLBOX Spezial IV: CALC Plus
  3.  
  4.  
  5. Integration von "Kurvendiskussion" PASCAL 11'86 und Sonderdruck
  6.             mit "CALC" Formelinterpreter PASCAL 8'87
  7.  
  8.  
  9. Überarbeitung von Peter Engels, am 4.9.1988
  10.  
  11.  
  12.  
  13.  
  14. Das Programm "DISCUSS" von Karsten Gieselmann, das eine umfangrei-
  15. che Kurvendiskussion erlaubt, wurde dahingehend erweitert, daß nun
  16. auch die Eingabe von Funktionstermen zur Laufzeit möglich ist. Als
  17. Kernstück dient hier der mathematische Compiler "CALC", der von um
  18. die Routinen zur symbolischen Differentiation ergänzt worden ist.
  19.  
  20. Bei diesen Änderungen stand neben der komfortableren Handhabung
  21. das Ziel im Vordergrund, das fertige Programm möglichst auf allen
  22. Rechnertypen und unter allen PASCAL - Varianten lauffähig zu
  23. halten. Darüberhinaus sollten die einzubindenden Prozeduren wenn
  24. möglich unverändert übernommen werden, damit sie weiterhin als
  25. Include-Files in anderen Programmen leicht verwendet werden
  26. können. Leider konnte ich diese Forderungen nicht vollständig
  27. erfüllen, da das fertige Programm bei dem Anwender sonst den
  28. Eindruck einer unausgereiften Rohfassung hinterlassen hätte.
  29.  
  30. Entwickelt wurde das Programm auf einem Apple II+ mit IBS AP22
  31. Karte unter CP/M80 und Turbo 3.0 und anschließend für die normale
  32. SoftCard umgeschrieben und getestet. Anschließend um ein DRAW.INC
  33. für PC-CGA Karte erweitert und ebenfalls getestet.
  34.  
  35. Das Programm weist in der vorliegenden Fassung gegenüber der
  36. Originalversion die folgenden Erweiterungen auf:
  37.  
  38. - Eingabe eines bis zu 128 Zeichen langen Funktionsterms zur Lauf-
  39. zeit, der jederzeit geändert werden kann. Die maximale Zeichenzahl
  40. kann in CALCTYPE durch Ändern der Stringlänge von Calc_String auf
  41. bis zu 255 Zeichen erhöht werden. Dadurch gehen allerdings etwa
  42. 1.8 kB Speicherplatz für den Heap verloren (bei CP/M rechnet man
  43. noch mit jedem kB!).
  44.  
  45. - symbolische Berechnung der ersten beiden Ableitungen, die auf
  46. dem Bildschirm ausgegeben und bei allen Berechnungen benutzt
  47. werden (der maximale Grad ist in maxgrad im File DISCUSS
  48. festgelegt und kann erhöht werden).
  49.  
  50. - sämtliche Eingaben erfolgen als String über die ebenfalls von
  51. Karsten Gieselmann geschriebene Prozedur "READSTR", so daß ein
  52. optimaler Eingabekomfort gewährleistet ist.
  53.  
  54. - bei der Eingabe von numerischen Werten, wie z.B. den Intervall-
  55. grenzen, wird ebenfalls mit "CALC" gearbeitet, so daß man jetzt
  56. auch 1/7 statt 0.1428571 oder einfach PI anstelle von 3.141592654
  57. eingeben kann. Es steht der gesamte Befehlsvorrat von CALC zur
  58. Verfügung. Die maximal mögliche Zeichenzahl beträgt 30, wozu ich
  59. aber später noch etwas sagen muß.
  60.  
  61. - alle Eingaben werden im Rahmen des Möglichen auf Zulässigkeit
  62. geprüft
  63.  
  64. - die Integrationsroutine erlaubt nun auch die Berechnung von
  65. Rotationsvolumen, Mantelfläche und Bogenlänge.
  66.  
  67. - das Modul DRAW.INC wurde so erweitert, daß auch die ersten
  68. beiden Ableitungen grafisch dargestellt werden können, und
  69. jederzeit durch Drücken einer beliebigen Taste ein Abbruch möglich
  70. ist.
  71.  
  72. - die Tabellenausgabe kann mit Ctrl-S angehalten werden. Dabei
  73. wird aber nicht die Compiler-Option $C+ benutzt, um einen voll-
  74. ständigen Programmabruch zu vermeiden.
  75.  
  76. - alle Berechnungen, mit Ausnahme der Integration, können durch
  77. Drücken einer beliebigen Taste abgebrochen werden.
  78.  
  79. - bei der Aufforderung, eine beliebige Taste zu drücken, wird bei
  80. gültigen Eingaben das Menu übersprungen, und gleich in den
  81. entsprechenden Programmteil verzweigt.
  82.  
  83. Die neu hinzugekommenen Turbo-spezifischen Prozeduren und Varia-
  84. blen habe ich auf ein Minimum beschränkt. Es werden benutzt:
  85.  
  86. 1) Delay(n) - im Hello - File
  87.  
  88. 2) ClrEol - um den Bildschirm bei Fehlermeldungen zu säubern
  89.  
  90. 3)ConOutPtr - in den Grafik-Routinen "DRAW3.INC" und "DRAW4.INC",
  91. die speziell für Apple II+/e Rechner angepaßt sind.
  92.  
  93.  
  94. Ich möchte nun für jedes eingebundene Modul die erforderlichen
  95. Änderungen angeben, die aber allesamt die Routinen in keiner Weise
  96. spezialisieren, sondern lediglich Verbesserungen für jeden Anwen-
  97. dungsfall bringen, so daß die einzelnen Teile auch problemlos und
  98. ohne Änderung in anderen Programmen verwendet werden können.
  99. Darüberhinaus werde ich auch auf eventuelle Bugs eingehen, die
  100. berichtigt wurden.
  101.  
  102.  
  103.  
  104. 1) READSTR.INC  Heft 3/87 S.82,83 - Karsten Gieselmann
  105.  
  106. Sollte die Eingabe vom aufrufenden Programm abgewiesen werden, so
  107. daß an der gleichen Bildschirmposition eine erneute Eingabe erfol-
  108. gen muß, so wird zwar der String aber nicht der Bildschirm
  109. gelöscht.
  110.  
  111. Abhilfe : zwischen Zeile 45 und 46 einfügen
  112.  
  113. 45'  : Write(' ' : MaxLen); Gotoxy(succ(x),y);
  114.  
  115.  
  116.  
  117.  
  118. 2) CALC und Hilfsfiles - Karsten Gieselmann, Michael Ceol
  119.  
  120. Zunächst die Bugs :
  121.  
  122. I) Schaltet man die RANGE - CHECK - Option $R+ ein, so steigt Calc
  123. in der Prozedur SearchSymTab regelmäßig mit einem Out Of Range
  124. Error aus. Ich habe daher die gesamte Prozedur umgeschrieben,
  125. wodurch sich leider auch Änderungen in den Files CALCCONS,
  126. CALCTYPE und CALCVAR ergeben :
  127.  
  128. i) Die Prozedur SearchSymTab wird ersetzt durch :
  129.  
  130. PROCEDURE SearchSymTab;
  131.  
  132. VAR symok: BOOLEAN;
  133.  
  134. BEGIN
  135.   symok := FALSE;
  136.   symbol := calc_err;
  137.   WHILE (symbol < calc_end) AND NOT symok DO
  138.     BEGIN
  139.       symbol := Succ(symbol);
  140.       symok := ident = calc_ids(.symbol.);
  141.     END;
  142.   IF NOT symok
  143.     THEN
  144.       symbol := calc_err
  145. END;
  146.  
  147.  
  148. ii) In CALCCONS entfällt Zeile 6, und damit das lästige Abzählen
  149. der implementierten Funktionen
  150.  
  151.  
  152. iii) In CALCTYPE ergänzt man Zeile 29 zu :
  153. ..., Calc_Rez, Calc_Fak, Calc_End);
  154.  
  155.  
  156. iv) In CALCVAR ergänzt man Zeile 47 zu :
  157.   ..., 'REZ' , 'FAK' , '@ende@');
  158.  
  159.  
  160. II) Reicht der Speicherplatz nicht aus, um eine neue Variablen-
  161. tabelle anzulegen, so wird der Zeigervariablen, die auf diese
  162. Tabelle zeigt, der Wert Nil zugewiesen. Alle Hilfsroutinen, die
  163. mit Variablentabellen arbeiten, nehmen darauf keine Rücksicht, so
  164. daß Systemabstürze in diesen Fällen unvermeidbar sind. Man ändere
  165. daher in CALCUTIL folgende Zeilen :
  166.  
  167. 85: BEGIN if vartab <> nil THEN dispose(vartab);
  168. 86: vartab := nil END;
  169.  
  170. Zwischen Zeile 96 und 97 :
  171. 96': if vartab <> nil THEN BEGIN
  172.  
  173. Zwischen Zeile 99 und 100
  174. 99': END ELSE SearchVarTab := 0
  175.  
  176. Zwischen Zeile 111 und 112 :
  177. 111': if vartab <> nil THEN BEGIN
  178.  
  179. Zwischen Zeile 119 und 120 :
  180. 119': END ELSE AddToVarTab := -1
  181.  
  182. Zwischen Zeile 128 und 129 :
  183. 128': if vartab <> nil THEN BEGIN
  184.  
  185. Zwischen Zeile 132 und 133 :
  186. 132' : END
  187. 132'': ELSE CalcError(0, 'Wertzuweisung an unbekannte Variable')
  188.  
  189.  
  190. III) In CALC muß die Zeile 354 wie folgt geändert werden :
  191.  
  192. 354:  Calc_Dvd : If Trunc(x) <> 0 THEN
  193.  
  194. IV) In CALC muß die Zeile 357 wie folgt geändert werden :
  195.  
  196. 357:  Calc_Mod : If Trunc(x) <> 0 THEN
  197.  
  198. V) CALC erlaubt auch die Eingabe von numerischen Werten, die
  199. unvollständig sind oder außerhalb des zulässigen Bereichs liegen,
  200. ohne auf den Fehler aufmerksam zu machen. Man ändere daher in CALC
  201. die Prozedur GetNumber wie folgt ab :
  202.  
  203.  
  204. i) Zeile 108 wird ergänzt zu :
  205.  
  206.  
  207. 108:    NumberEnd, posi : INTEGER;
  208.  
  209.  
  210. ii) zwischen die Zeilen 111 und 112 füge man die folgenden ein :
  211.  
  212.          NumberStr := NumberStr + '     ';
  213.          posi := 1;
  214.          while (not (numberstr(.posi.) in (.'e','E'.)))
  215.            and (posi < length(numberstr))
  216.          do posi := succ(posi);
  217.          if numberstr(.posi.) in (.'e','E'.) THEN
  218.          BEGIN
  219.            if numberstr(.posi+1.) in (.'+','-'.)
  220.               THEN posi := succ(posi);
  221.            if not (numberstr(.posi+1.) in (.'0'..'9'.))
  222.              THEN error(StrPos+posi,'unvollstaendiger Ausdruck')
  223.              else
  224.              if
  225.               ((numberstr(.posi+1.) = '3') and
  226.                (numberstr(.posi+2.) in (.'7'..'9'.)))
  227.                or
  228.               ((numberstr(.posi+1.) > '3') and
  229.                (numberstr(.posi+2.) in (.'0'..'9'.)))
  230.               THEN
  231.                 error(StrPos+posi,'ausserhalb des Real-Bereichs');
  232.          END;
  233.  
  234.  
  235.  
  236. Außerdem lassen sich an CALC drei kleine Verbesserungen vornehmen,
  237. die die Arbeit mit diesem Programm etwas erleichtern :
  238.  
  239.  
  240. a) Bei den Calc-Variablen wird zwischen Groß- und Kleinschreibung
  241. unterschieden. Abhilfe schaffen folgende Ergänzungen :
  242.  
  243. Man fügt in CALCUTIL jeweils zwischen die Zeilen 96 und 97 bzw.
  244. 111 und 112 die folgende ein :
  245.  
  246. For i := 1 to Length(id) do id(.i.) := upcase(id(.i.));
  247.  
  248. b) Eine Eingabe einer Real-Zahl, die betragsmäßig kleiner als 1
  249. ist, erfordert immer eine führende 0. Man ergänze daher in CALC
  250. Zeile 149 zu :
  251.  
  252. 149 : '0'..'9','.' : BEGIN Getnumber; ...
  253.  
  254. c) PI und E sind als Basis einer Exponentialfunktion nicht
  255. erlaubt. Man ergänze in CALC Zeile 245 zu :
  256.  
  257. 245 : IF LastSymbol in (.Calc-Pi,Calc_e,Calc_Const ...
  258.  
  259.  
  260.  
  261. Nun zu den Änderungen, die sich für die Anpassung an DISCUSS als
  262. günstig erwiesen haben :
  263.  
  264. a) Um bei Fehleingaben den Bildschirm zu säubern, wurden die
  265. Prozeduren "Error" in CALC und "CalcError" in CALCUTIL wie folgt
  266. ergänzt :
  267.  
  268.  
  269.    PROCEDURE Error (ErrPos: INTEGER; ErrMsg: Calc_String);
  270.  
  271.    BEGIN
  272.       IF NOT ParsError THEN
  273.       BEGIN
  274.          WriteLn; Write('*** Fehler in CompileExpression:');
  275.          ClrEol; WriteLn;
  276.          Write (' ',Expr);ClrEol; Writeln;
  277.          Write (' ':ErrPos, '^'); ClrEol; Writeln;
  278.          Write (ErrMsg, '!'); Clreol; Writeln; ClrEol;
  279.          WriteLn; ClrEol
  280.       END;
  281.       ParsError := TRUE;  Symbol := Calc_Err
  282.    END;
  283.  
  284.  
  285. PROCEDURE CalcError (ErrNum: INTEGER; Message: ErrString);
  286.  
  287. BEGIN
  288.   WriteLn;  Write('*** Laufzeitfehler:'); Clreol; WriteLn;
  289.   Write('    ');
  290.   CASE ErrNum OF
  291.     1: Write('Gleitkommaueberlauf');
  292.     2: Write('Division durch Null');
  293.     3: Write('Argumentfehler in ');
  294.     ELSE ;
  295.   END;
  296.   Write(' ', Message, ' !'); ClrEol;
  297.   WriteLn; ClrEol;
  298.   WriteLn; ClrEol;
  299.   CalcResult := FALSE;                 (* Auswertung abbrechen ! *)
  300. END;
  301.  
  302.  
  303.  
  304. 3) CALCDERI - Peter Engels
  305.  
  306. Die Routinen zur symbolischen Differentiation wurden von mir
  307. eingefügt. Hier noch einmal eine kurze Funktionsbeschreibung der
  308. neu hinzugekommenen Routinen :
  309.  
  310. Die von "Calc" erzeugten Programme werden zunächst in die UPN-
  311. Notation übersetzt und anschließend als linear verkettete Liste im
  312. Speicher abgelegt. Dabei ist das erste Element der Liste ein
  313. Listenanker, der nur den Verweis auf den ersten gültigen Eintrag
  314. in der Liste enthält.
  315.  
  316. Ein Beispiel : f(x) = (7+x)*x^3 wird in folgender Reihenfolge
  317. abgespeichert : @ 7 x + x 3 ^ * . Das @-Zeichen soll dabei den
  318. Listenanker darstellen.
  319.  
  320. Da man eine solche linear verkettete Liste nur von Anfang bis Ende
  321. durchlaufen kann, ist folglich der für die Differentiation als
  322. erstes bedeutsame Operator '*' erst bekannt, wenn man über dessen
  323. Operanden bereits hinweggelesen hat. Daher schien es mir für die
  324. gestellte Aufgabe am einfachsten, zunächst die Liste vollständig
  325. zu invertieren, so daß der letzte Operator als erstes Element der
  326. Liste erscheint. Diese Aufgabe übernimmt die Funktion "invert",
  327. die das vorliegende Calc-Programm invertiert, das für das gewählte
  328. Beispiel danach so aussieht : @ * ^ 3 x + x 7 . Damit ist der
  329. erste Operator eine Multiplikation, und die Ableitungsregel
  330. bekannt. Man beachte, daß bei dieser Invertierung natürlich auch
  331. die Reihenfolge der Operanden vertauscht worden ist, was
  332. insbesondere bei nicht kommutativen Operationen zu berücksichtigen
  333. ist.
  334.  
  335. Die Aufgabe des Ableitens wird von der Prozedur "calcderivation"
  336. übernommen, deren Kernstück die rekursive Prozedur "derive" ist,
  337. und die in dem Include_File "CALCDERI.PAS" enthalten ist.
  338.  
  339. Nachdem also nun der erste Operator bekannt ist, müssen als
  340. nächstes die beiden zugehörigen Operanden gefunden werden. Der
  341. erste Operand ist sofort klar : er beginnt mit dem nächsten
  342. Listenelement und wird im Programm als Zeiger pptra gespeichert.
  343. Der Anfang des zweiten Operanden ist nicht unmittelbar anzugeben,
  344. da man nicht weiß, wie lang der erste Operand ist. Er wird mit
  345. Hilfe der Funktion "endof" gesucht, die ebenso wie "invert" global
  346. im Programm definiert ist, da diese Funktionen mehrfach von
  347. verschiedenen Programmteilen benutzt werden. endof liefert einen
  348. Zeiger zurück, der auf den letzten Eintrag der zu pptra gehörenden
  349. Anweisung zeigt. pptrb ist dann der Zeiger auf den nächsten
  350. Eintrag. Damit müßte die Ableitungsregel für unser Beispiel dann
  351. so aussehen :
  352.  
  353. newop(calc_add);
  354. newop(calc_mul);
  355. derive(pptrb);   <-- rekursiver Aufruf
  356. push(pptra);
  357. newop(calc_mul);
  358. derive(pptra);   <-- rekursiver Aufruf
  359. push(pptrb);
  360.  
  361. Das ist genau die Umsetzung der bekannten Produktregel.
  362.  
  363. Die Prozedur "newop" fügt ein neues Listenelement mit der angege-
  364. benen Anweisung an die Liste für die Ableitung an, die Prozedur
  365. "push" fügt den gesamten zum Zeiger gehörenden Ausdruck an die
  366. Liste an. Als nächstes wird also nun pptrb differenziert. Das ist
  367. in unserem Beispiel die Anweisung : + x 7 . Die Operation ist eine
  368. Addition, also :
  369.  
  370. newop(calc_add);
  371. derive(pptra);
  372. derive(pptrb);
  373.  
  374. Zu pptra gehört der Eintrag x, der die Variable darstellt :
  375.  
  376. newconst(1.0);
  377.  
  378. und zu pptrb gehört der Eintrag 7, also :
  379.  
  380. newconst(0.0);
  381.  
  382. Damit ist die Ableitung des Zeigers pptrb der Multiplikation
  383. erledigt, so daß jetzt pptra in die Liste eingetragen wird. Nun
  384. muß pptra differenziert werden, wozu der Eintrag ^ 3 x gehört. Es
  385. handelt sich in diesem Beispiel also um eine Potenz. Da es zur
  386. Ableitung von Exponential- und Potenzfunktionen in Heft 7/87 in
  387. Zusammenhang mit dem in (2) veröffentlichten Programm bereits zu
  388. einem kritischen Leserbrief gekommen ist, will ich hierzu etwas
  389. Mathematik bringen und die Ableitungsregel für jeden nur denkbaren
  390. Fall angeben :
  391.  
  392. (f^g)' = (exp(g*ln(f))' = (g'*ln(f) + g*f'/f)*(f^g)
  393.  
  394. Mit dieser Regel läßt sich von 2^3 bis x^x alles differenzieren,
  395. da alle anderen Ableitungsregeln nur Spezialfälle dieser einen
  396. Regel sind, die einzig von der Kettenregel lebt. Also :
  397.  
  398. newop(calc_mul);
  399. newop(calc_pow);
  400. push(pptra);
  401. push(pptrb);
  402. newop(calc_add);
  403. newop(calc_dvd);
  404. push(pptrb);
  405. newop(calc_mul);
  406. push(pptra);
  407. derive(pptrb);
  408. newop(calc_mul);
  409. derive(pptra);
  410. newop(calc_ln);
  411. push(pptra);
  412.  
  413. Da beide Operanden keine neuen Operationen enthalten, ist die
  414. Ableitung beendet. Die Prozeduren "newop", "newconst" und "push"
  415. sind so angelegt, daß sie gleich eine Liste erzeugen, die
  416. unmittelbar von Calc ausgewertet werden kann. Die Liste für unser
  417. Beispiel sieht nun so aus :
  418.  
  419. @ 7 x + 3 ln 0 * 1 3 * x / + x 3 ^ * * x 3 ^ 0 1 + * +
  420.  
  421. Nach ähnlichen Regeln lassen sich alle differenzierbaren Funktio-
  422. nen mit calcderivation ableiten. Bleibt die Frage, was differen-
  423. zierbare Funktionen sind. Mathematisch gesehen, ist diese Frage
  424. leicht zu beantworten, im vorliegenden Fall muß man sich die Ant-
  425. wort aber etwas schwerer machen. Calc verfügt über eine ganze Rei-
  426. he von Funktionen, die urspünglich nur auf N oder Z leben und
  427. damit natürlich nicht differenzierbar sind. Durch die Art der
  428. Implementation in Calc sind diese Funktionen aber auf ganz R fort-
  429. gesetzt worden. Auf R-Z sind diese Funktionen lokal konstant und
  430. somit differenzierbar, die Ableitung ist Null. Ich habe das in
  431. meinem Programm in dieser Form berücksichtigt, wer hier strenger
  432. sein will, bricht mit einer Fehlermeldung ab. Eine weitere Sonder-
  433. stellung nimmt die Funktion ABS(X) ein. Sie ist bis auf Null über-
  434. all differenzierbar, ihre Ableitung ist SIGN(X). Die Funktion
  435. SIGN(X) muß dazu in "MATHFUNC.PAS" und Calc aufgenommen werden.
  436.  
  437. Zurück zum Beispiel. Man sieht sehr deutlich, daß bei sturer
  438. Anwendung der Ableitungsregeln zwar richtige Ableitungsterme ent-
  439. stehen, die allerdings reichlich viel Müll in Form von toten Zwei-
  440. gen und überflüssigen Operationen enthalten.
  441.  
  442. Diesem Müll wird vom Programm an zwei Stellen begegnet. Zunächst
  443. werden schon in calcderivation Konstanten gesondert behandelt, so
  444. daß aus x^3 bei der Ableitung auch 3*x^2 wird. Danach wird die
  445. Liste von der Funktion "calcsimplify" weiterbearbeitet. Hier wer-
  446. den Terme wie 2*3 berechnet und durch das Ergebnis ersetzt. Außer-
  447. dem werden Ausdrücke, die die Konstanten 0.0 oder 1.0 enthalten
  448. entsprechend vereinfacht. Extreme Schwierigkeiten bereitet bei der
  449. Vereinfachung das Kommutativgesetz, von dem ich bislang nur einige
  450. Spezialfälle berücksichtigen konnte.
  451.  
  452. Insgesamt arbeitet calcsimplify recht zufriedenstellend, wenn auch
  453. einige Sonderfälle gar nicht oder nur unzureichend berücksichtigt
  454. worden sind. Hier ist noch sehr viel Platz für eigene Ideen,
  455. Ergänzungen und auch für Verbesserungen.
  456.  
  457. Mit diesen Routinen lassen sich bereits numerische Berechnungen
  458. durchführen, die eigentliche Auswertung wird dabei wieder von Calc
  459. übernommen. Da ich davon ausgegangen bin, daß alle Listen entweder
  460. von calc oder von calcderivation erstellt worden sind, habe ich an
  461. keiner Stelle der Routinen einen Syntax-Check vorgesehen.
  462.  
  463. Wer aber auch den Ableitungsterm auf dem Schirm sehen oder auf
  464. Diskette speichern will, muß das Calc_Programm wieder in AOS-
  465. Schreibweise zurückübersetzen. Diese Aufgabe übernimmt die eben-
  466. falls rekursive Prozedur "calcaos". Diese Routine ist für die
  467. Bildschirmausgabe konzipiert, sie kann durch kleine Änderungen
  468. aber auch für die Ausgabe in einen String benutzt werden, der dann
  469. zur weiteren Bearbeitung zur Verfügung steht. Hierbei besteht
  470. allerdings die Gefahr, daß insbesondere bei höheren Ableitungen
  471. leicht das Limit von 255 Zeichen überschritten wird.
  472.  
  473.  
  474. Zur Handhabung der Routinen :
  475.  
  476. Alle drei Routinen sind so angelegt, daß sie unmittelbar mit Calc-
  477. Programmen arbeiten können und auch wieder für Calc lesbare
  478. Programme erzeugen.
  479.  
  480. 1) Der Prozedur calcsimplify wird als Parameter der Zeiger auf das
  481. zu vereinfachende Calc-Programm übergeben. Geht etwas schief, weil
  482. z.B. ln(-10) berechnet werden muß, so ist CalcResult = False und
  483. das Calc-Programm verloren!
  484.  
  485.  
  486. 2) Die Funktion calcderivation benutzt drei Parameter : zunächst
  487. das Calc-Programm, dann die Variablenliste und schließlich die
  488. Variable, nach der differenziert werden soll als String. Sie
  489. liefert als Wert den Zeiger auf die Ableitung zurück. Geht etwas
  490. schief, so ist das Ergebnis Nil und CalcResult = False.
  491.  
  492. 3) Der Prozedur calcaos werden das Calc-Programm und die
  493. Variablenliste übergeben. Reicht der Speicher auf dem Heap nicht
  494. aus, erfolgt keine Ausgabe.
  495.  
  496. Abschließend noch ein Wort zum Laufzeitverhalten. Vergleicht man
  497. die Laufzeit der von calcderivation erzeugten Programme mit der
  498. Laufzeit von numerisch berechneten Ableitungen, so zeigt sich kein
  499. einheitliches Bild. Es gibt Situationen, in den calcderivation bis
  500. zu 70 mal schneller als die numerische Variante ist, andererseits
  501. aber auch solche, bei denen die Auswertung der symbolischen
  502. Ableitung länger braucht. Das hängt stark von dem vorliegenden
  503. Funktionsterm und der Ordnung der Ableitung ab. In jedem Falle
  504. aber liefert die symbolische Ableitung ein genaueres Ergebnis als
  505. die numerische Variante.
  506.  
  507.  
  508. Im Zusammenhang mit CALCDERI wurde auch die Funktionensammlung
  509. MATHFUNC erweitert bzw. geändert. Da die Ableitung von ABS(X) im
  510. wesentlichen SIGN(X) ist, wurde diese Funktion ergänzt :
  511.  
  512. FUNCTION sign(x : real) : real;
  513. BEGIN
  514.   if x > 0.0 then sign := 1.0
  515.              else if x < 0.0 then sign := -1.0
  516.                              else sign := 0.0
  517. END;
  518.  
  519. Außerdem erwies sich die Funktion x_hoch_y als sehr fehleranfäl-
  520. lig. Leider kann ich hier nicht auf Zeilennummern verweisen, da
  521. die bereits geänderte Fassung nicht abgedruckt wurde. Daher
  522.  
  523. FUNCTION x_hoch_y (x, y: REAL): REAL;
  524.  
  525. VAR ganz_y: INTEGER;
  526.  
  527. BEGIN
  528.   IF (x <> 0.0) OR (y <> 0.0) THEN
  529.     IF x > 0.0 THEN
  530.     if abs(y*ln(abs(x))) > 86.0 THEN
  531.      CalcError(3,'x_hoch_y(x,y): ABS(y*ln(X)) > 86.0')
  532.      else  x_hoch_y := Exp(y * Ln(x))
  533.     ELSE
  534.       BEGIN
  535.         ganz_y := Trunc(y);
  536.         IF ABS(y) > ABS(ganz_y) THEN
  537.           CalcError(3, 'x_hoch_y(x,y):'+
  538.             ' nur ganzzahlige Exponenten zulaessig')
  539.         ELSE
  540.           IF x <> 0.0 THEN
  541.             IF (ganz_y MOD 2) = 0 THEN
  542.               if abs(y*ln(abs(x))) > 86.0 THEN
  543.                CalcError(3,'x_hoch_y(x,y): ABS(y*ln(X)) > 86.0')
  544.               else x_hoch_y :=  Exp(Ln(ABS(x)) * y)
  545.             ELSE
  546.              if abs(y*ln(abs(x))) > 86.0 THEN
  547.                CalcError(3,'x_hoch_y(x,y): ABS(y*ln(X)) > 86.0')
  548.              else x_hoch_y := -Exp(Ln(ABS(x)) * y)
  549.                      (* ungerader Exponent *)
  550.           ELSE
  551.             x_hoch_y := 0
  552.       END
  553.   ELSE
  554.     x_hoch_y := 1.0  (* 0^0 := 1 *)
  555. END;
  556.  
  557.  
  558. Die neu eingefügte Funktion 'Sign' muß natürlich CALC mitgeteilt
  559. werden. Da ich darüberhinaus die INT-Funktion vermißt habe, wurde
  560. diese gleich mit implementiert :
  561.  
  562. a) in CALCTYPE zwischen Zeile 28 und 29 einfügen :
  563.  
  564. 28' : Calc_Sig, Calc_Int,
  565.  
  566. b) in CALCVAR zwischen Zeile 46 und 47 einfügen :
  567.  
  568. 46': 'SIGN' , 'INT' ,
  569.  
  570. c) in CALC zwischen Zeile 391 und 392 einfügen :
  571.  
  572. 391 ': Calc_sig : x := sign(x);
  573. 391'': Calc_int : x := int(x);
  574.  
  575.  
  576. Wer weitere Funktionen in CALC implementieren will, muß beachten,
  577. daß die neuen Funktionen in den Listen nicht einfach hinten ange-
  578. hängt werden dürfen, sondern in der Mitte der Liste eingebunden
  579. werden müssen. Dazu bieten sich die oben angegebenen Stellen an.
  580.  
  581.  
  582.  
  583. 4) DISCUSS - Karsten Gieselmann
  584.  
  585. Hier waren selbstverständlich die meisten Änderungen notwendig,
  586. die ich aufgrund ihrer Fülle nicht alle im Detail ansprechen kann.
  587.  
  588. Die Zeilenangaben beziehen sich auf den Nachtrag in Heft 8/87.
  589.  
  590. Auch hier zunächst die Bugs :
  591.  
  592. 1) In FLEX.INC muß Zeile 30 lauten :
  593.  
  594. 30: if abs(fn(x,1)) > eps then
  595.  
  596. 2) In SOLVE.INC sollte Zeile 36 lauten :
  597.  
  598. 36: until (abs(x-lastx) < eps * abs(x)) or (y = lasty);
  599.  
  600. -----------------------------------------------------------------
  601.  
  602. Nun zu den sonstigen Änderungen :
  603.  
  604. a) In DISCUSS müssen die einzelnen IncludeFiles hinzugefügt
  605. werden, sowie weitere erforderliche globale Variable deklariert
  606. werden (s. Listing)
  607.  
  608. b) In DEF entfallen die Zeilen 11 bis 13 und 21 bis 24. Außerdem
  609. habe ich wegen der zu großen Häufigkeit von Laufzeitfehlern die
  610. Rechengenauigkeit wieder auf den ursprünglichen Wert reduziert.
  611.  
  612. c) Das File FUNCTION.INC enthält nun zusätzlich die Prozedur zum
  613. Einlesen und Ausgeben des Funktionsterms, sowie die geänderte
  614. Routine zur Funktionswertberechnung mittels CALC. Bei der Eingabe
  615. von f(x) ist zu beachten, daß das ';' am Ende vom Programm automa-
  616. tisch ergänzt wird, und folglich nicht mit eingegeben werden muß.
  617. (s. Listing)
  618.  
  619. d) Das File SOLVE.INC hat bereits in PASCAL Heft 3 einige Verbes-
  620. serungen erfahren, die allerdings ebensoviel geschadet, wie
  621. genutzt haben. Die ursprünglich veröffentlichte Version war nicht
  622. in der Lage, Nullstellen geraden Grades von f zu finden, so daß
  623. die Prozedur Solve dahingehend erweitert wurde. Dann ermittelt
  624. diese Prozedur aber auch die Nullstellen gerader Ordnung der
  625. Ableitungen, um Extrem- und Wendestellen zu finden, was natürlich
  626. Unfug ist, denn solche ausgezeichneten Punkte liegen nur bei Null-
  627. stellen ungerader Ordnung vor (Vorzeichenwechsel der entsprechen-
  628. den Ableitung ist hinreichendes Kriterium!!!). Warum also diese
  629. Nullstellen gerader Ordnung mitberechnen - das kostet nur unnötig
  630. Zeit! Ändert man das ab, so kann sogar auf die dritte Ableitung
  631. vollständig verzichtet werden, was in dem Fall der symbolischen
  632. Differentiation wertvollen Speicherplatz spart. Insgesamt werden
  633. durch diese Änderung alle ausgezeichneten Punkte wieder wesentlich
  634. schneller gefunden. Sattelpunkte findet der Rechner dann natürlich
  635. nur noch im Zusammenhang mit der Wendestellenbestimmung.
  636.  
  637. e) Das File FORMULA.INC ist neu hinzugekommen und enthält die Pro-
  638. zeduren zur Eingabe von numerischen Werten, die ebenfalls von CALC
  639. ausgewertet werden. Zur Eingabe des Strings wird auch hier die
  640. Prozedur "ReadStr" verwendet. Diese Prozedur verlangt allerdings
  641. eine genaue Angabe, an welcher Bildschirmposition die Eingabe
  642. erfolgen soll. Das kann jedoch bei den hier vorkommenden Fällen
  643. nicht sicher angeben werden, da man mit Eingabefehlern rechnen
  644. muß, die zu Fehlermeldungen führen und jede Eingabemaske zerstö-
  645. ren. Man ist daher gezwungen, alle Eingaben zeilenweise vorzuneh-
  646. men, wobei sich die Zeile aus der jeweiligen Bildschirmposition
  647. des Cursors ergibt. Kritisch ist daher die y- Position, die x-
  648. Position kann immer mit 1 angenommmen werden. Das Problem ist
  649. sofort gelöst, wenn die PASCAL-Version auf dem verwendeteten
  650. Rechner über eine WhereY-Prozedur verfügt, wie sie in der IBM
  651. Version vorgesehen ist. Für Apple II und Schneider Rechner wurde
  652. eine entsprechende Prozedur in PASCAL veröffentlicht. Da aber
  653. nicht für jeden Rechner eine derartige Prozedur implementiert ist,
  654. mußte ich in die Trickkiste greifen : Als y-Position wird einfach
  655. 0 angegeben. Das führt dazu, daß zumindest in Turbo 3.0 ein Gotoxy
  656. einfach auf die gegenwärtige Zeile positioniert. Überschreitet man
  657. dann allerdings das Zeilenende, spielt ReadStr "verrückt", so daß
  658. die Eingabezeile auf 30 Zeichen beschränkt wurde. (s. Listing)
  659.  
  660. f) Die Funktion INTEGRAL wurde so geändert, daß nun zusätzlich,
  661. neben der Flächenberechnung, auch die Berechnung von Rotations-
  662. volumen, Mantelfläche und Bogenlänge erfolgen kann.
  663.  
  664. g) in allen Files wurden die Sonderzeichen zur Darstellung der
  665. deutschen Umlaute entsprechend ersetzt, da sie zu Schwierigkeiten
  666. führen können. So ist unter CP/M ein File, daß ein großes UE
  667. enthält nicht mehr lesbar, da diesem Zeichen der Code H9A
  668. entspricht, was im Turbo-Pascal-Editor der File-Ende Markierung
  669. gleichkommt!!! Ich konnte mir nur mit einem Disk-Editor helfen,
  670. indem ich diese Bytes auf einen lesbaren Wert (H30) gesetzt habe.
  671. (Vielleicht sollte man in Zukunft grundsätzlich auf derartige
  672. Spezialitäten verzichten !)
  673.  
  674. h) das File INTERVAL wurde so geändert, daß es mit der Prozedur
  675. "FormulaLn" zusammenarbeitet.
  676.  
  677. i) Die Files DRAW und DRAW2 wurden ebenfalls geändert, um trotz
  678. auftretender Laufzeitfehler einen ordentlichen Funktionsgraphen zu
  679. garantieren. Außerdem wurde die Möglichkeit geschaffen, neben dem
  680. Graphen von f auch die Graphen der ersten beiden Ableitungen zu
  681. zeichnen. Darüberhinaus wurden die Files DRAW3 und DRAW4 erstellt,
  682. die an die Hardware der Apple II+/e Rechner angpaßt sind. Diese
  683. Prozeduren benutzen die in (7) abgedruckten Hilfsroutinen zur
  684. Steuerung der 6502 CPU und erfordern daher ohnehin Turbo-Pascal
  685. als Pascalversion, so daß die hier ebenfalls Turbo-spezifische
  686. Variable "ConOutPtr" keine zusätzliche Einschränkung mit sich
  687. bringt. Diese Variable muß deshalb benutzt werden, weil nach
  688. meinen (spärlichen) Erfahrungen mit Apple IIe Rechnern eine Bild-
  689. schirmausgabe im Grafikmodus zu merkwürdigen Resultaten führt.
  690. Daher wurde vorsorglich jegliche Bildschirmausgabe unterbunden,
  691. die im Falle eines Laufzeitfehlers auftreten würde.
  692.  
  693. j) Das File MENU3 wurde vollständig überarbeitet. Insbesondere
  694. wurden die nichtssagenden Ziffern zur Programmauswahl durch leicht
  695. zu merkende Buchstaben ersetzt. MENU3 ruft auch die neue Prozedur
  696. HELLO auf, die den Programmnamen und einen Copyright- Vermerk
  697. ausgibt. Gibt man auf die Aufforderung "Weiter mit beliebiger
  698. Taste..." ein gültiges Zeichen ein, so wird das Menu übersprungen,
  699. und gleich der entsprechende Programmpunkt angewählt.
  700.  
  701.  
  702.  
  703. Abschließend noch ein Tip speziell für Turbo 3.0:
  704.  
  705.  
  706. Einen Programmabsturz habe ich zwar nur bei ganz unüberlegten
  707. Eingaben beobachtet, aber leider häufen sich bei bestimmten
  708. Funktionstermen die Fehlermeldungen, insbesondere in der Funktion
  709. x_hoch_y. Es empfiehlt sich daher alle Anweisungen, die eventuell
  710. auftretende RunTime-Fehler abfangen, aus dem Programm zu entfer-
  711. nen, und stattdessen den entsprechenden Error_Handler (4) oder (5)
  712. einzubinden. Dadurch wird das Programm natürlich auch weiter
  713. beschleunigt.
  714.  
  715.  
  716. Nun noch ein Paar Worte zur Implementation auf Apple Rechnern. Da
  717. mein Apple Rechner mit einer 8MHz IBS AP22 Karte und einer 304kB
  718. großen Ram-Floppy ausgestattet ist, war es für mich überhaupt kein
  719. Problem das vorliegende Programm zu entwickeln und zu compilie-
  720. rern. Was aber tun, wenn der gute alte Apple nicht "getunt" ist?
  721.  
  722. Nun, Mindestvoraussetzung sind in jedem Fall zwei Laufwerke sowie
  723. eine normale 2MHz SoftCard und die CP/M Version 2.23, die immerhin
  724. nochmals 4kB mehr Speicherplatz bereit stellt, als die Version
  725. 2.20. Da das Programm die zweite Grafikseite benutzt, die bei der
  726. SoftCard in der TPA liegt, muß das Programm mit einer Startadresse
  727. von H5000 auf Diskette compiliert werden. Dadurch verringert sich
  728. natürlich der freie Speicherplatz im RAM erheblich, so daß man
  729. zusätzlich auf die Overlay-Technik zurückgreifen muß. Wer mit der
  730. Version 2.20(B) arbeitet, muß entweder noch mehr Overlays benut-
  731. zen, wozu dann aber die Prozedurreihenfolge gut überlegt sein muß,
  732. oder er muß das Programm abspecken, indem z.B. auf die symbolische
  733. Differentiation verzichtet wird. Leider ist es unmöglich sowohl
  734. Turbo-Pascal, alle Quellfiles und das Compilat auf einer Diskette
  735. zu halten. Man fertige daher zwei Disketten an :
  736.  
  737. Disk A :  enthält TURBO,  DISCSOFT.PAS und CALC.PAS;  auf  dieser
  738. Diskette entsteht dann auch das Compilat
  739.  
  740. Disk B : enthält die restlichen Include Files
  741.  
  742. Die Bezeichnungen A und B beziehen sich auf das verwendete Lauf-
  743. werk. Trotz dieser Maßnahmen bleibt der freie Speicherbereich für
  744. Calc-Programme reichlich klein, so daß bei einigen Funktionen die
  745. dritte Ableitung nicht mehr berechnet werden kann. Das File
  746. DISCSOFT ist eine Variante des Files DISCUSS und speziell für
  747. diesen Fall vorbereitet, d.h. hierin sind alle OVERLAY- Anweisun-
  748. gen enthalten und die Include-Files werden in Laufwerk B: erwar-
  749. tet.
  750.  
  751.  
  752. Literatur :
  753.  
  754. (1)  Gieselmann, Karsten
  755.      Kurvendiskussion
  756.      PASCAL Heft 11/86 S.29ff
  757.  
  758. (2)  Schlöter, Martin
  759.      Symbolverarbeitung in Prolog
  760.      PASCAL Heft 4/87 S.58ff
  761.  
  762. (3)  Gieselmann, Karsten ; Ceol, Michael
  763.      CALC - ein mathematischer Compiler
  764.      PASCAL Heft 8/87 S.52ff
  765.  
  766. (4)  Gieselmann, Karsten
  767.      Eigene Fehlerbehandlung in Turbo-Pascal-Programmen
  768.      PASCAL Heft 9/87 S.94
  769.  
  770.  
  771. (5)  Engels, Peter
  772.      Eigene  Fehlerbehandlung  in  Turbo-Pascal-Programmen  unter
  773.      CP/M 80
  774.      PASCAL Heft 12/87 S.88
  775.  
  776.  
  777. (6)  Gieselmann, Karsten
  778.      Komfortable Stringeingabe
  779.      PASCAL Heft 3/87 S.82,83
  780.  
  781.  
  782. (7)  Engels, Peter
  783.      Turbo-Pascal und die 6502 des Apple II
  784.      PASCAL Heft 12/87 S.82,83
  785.  
  786.  
  787.  
  788. Sicherlich sind noch viele Ergänzungen möglich, von denen ich
  789. abschließend noch einige vorstellen möchte, die dem Anwender als
  790. Anregung dienen mögen :
  791.  
  792.  
  793. - Hardcopy der erzeugten Grafik und der Berechnungen
  794. - Beschriften der Grafik
  795. - Abspeichern und Einlesen von Funktion und Graph auf/von Disk
  796.  
  797. Wie man sieht, sind diese nützlichen Erweiterungen leider allesamt
  798. sowohl von der Hardware, als auch von der verwendeten Pascal-
  799. Version abhängig, so daß es wenig sinnvoll ist, diese Erweiterun-
  800. gen generell einzubauen.
  801.  
  802.  
  803.                                         Peter Engels
  804.