Mini-Skript
Transcrição
Mini-Skript
Fortran77-Grundlagen Wilhelm Kley 2. März 2016 1 Einführung Dieser kleine Text soll eine Übersicht über die Grundlagen von Fortran geben. Es ist keine vollständige Übersicht über alle Sprachelemente, umfasst aber die wichtigsten, die es ermöglichen sinnvoll und effizient zu programmieren. Der beschriebene Sprachumfang benutzt im wesentlichen Standard Fortran-77, bezieht aber einige Erweiterungen mit ein, die de facto alle gängigen F77-Compiler kennen. Im Text wird versucht, auf diese nicht dem Standard entsprechenden Features hinzuweisen. Ganz generell lässt sich hier schon sagen, dass die Fortran-Sprachelemente derart sind, dass nicht zwischen Groß- und Kleinschreibung unterschieden wird. Im Text wird ab und zu erneut darauf hingewiesen. 1.1 Problem Lösung mit Hilfe eines Computers Das Erstellen eines Programms zur Lösung physikalischer (oder auch allgemeinerer) Problemstellungen kann in mehrere Teilschritte unterteilt werden. 1. Problembeschreibung Mathematische Formulierung einer Phys. Fragestellung 2. Algorithmus-Entwicklung Folge von Anweisungen zur Lösung des Problems 3. Coding Übersetzen des Algorithmus in Programmiersprache (Fortran, C, Pascal, etc.) −→ Erstellung eines Computerprogramms (Quellcode) 4. Übersetzen (Compilieren) Quellcode −→ Maschinencode (Compile-Time) mit Hilfe eines Compilers. Der Compiler liefert Syntax-Fehler (z.B. keine Formulierung nach FTN-77) −→ weiter mit Punkt 3. 5. Starten des Programms Programm laufen lassen (Execution-Time) Bei Fehlermeldungen oder inkorrekten Ergebnissen −→ weiter mit Punkt 3 (Debugging) 1 1.2 Zeileneinteilung Die Programmzeile eines Fortran-Programms kann nicht in einem freien Format eingegeben werden, sondern muß nach einer festen Spalteneinteilung erfolgen. Das eigentliche Programm steht nur in den Spalten 7 bis 72. Alles was nach der 72.ten Spalte kommt wird vom Compiler ignoriert. Die wesentliche Zeilenstruktur gibt die folgende Tablle wieder: Spalte Inhalt einer Fortran-Anweisung 1-5 Nummernfeld für Formate und Anweisungsnummern label (siehe Text) Fortsetzungsspalte 6 bei einem Zeichen 6= 0 gehört eine Zeile zur vorherigen Häufig wird hier & das Und-Zeichen verwendet. 7 - 72 FTN-Anweisungen Kommentare im Quellcode werden in Fortran durch ein ’C’ bzw. ’c’ oder ein ’*’ in Spalte 1 gekennzeichnet. Nicht Standard: Innerhalb einer Zeile mit Anweisungen wird der Text nach einem ’ !’ (Ausrufezeichen) ignoriert und als Kommentar angesehen. 1.3 Programmerstellung & Compilierung ). Der Programmtext wird mit einem Text-Editor (vi, emacs) geschrieben und als program name.f abgespeichert. Hierbei bezeichnet program name einen beliebigen Namen des Programms. Fortran-77 Programm-Texte enden standardmässig auf ’.f’, Fortran-90 Programm-Texte auf ’.f90’ und C Programm-Texte auf ’.c’. Das Übersetzen eines Fortran-Programms geschieht mit Hilfe eines Compilers, d.h. eines Programms, welches den lesbaren (aus ASCI-Zeichen bestehenden) Quellcode in ein maschinenlesbares Programm überführt. Bei den üblichen Distributionen des Betriebssystems Linux (so beim CIP-Pool) ist der GNU Fortran-Compiler gfortran standardmäßig enthalten. Besteht das Programm z.B. aus den zwei Text-Dateien main.f und file1.f so lautet der Standardbefehl zur Übersetzung: gfortran -o progx main.f file1.f Damit werden die beiden Files main.f und file1.f zusammen compiliert und das Programm progx erzeugt. Die Option -o prog name schreibt das fertige Programm in den File prog name. Andere Optionen des Compilers gfortran werden mit: man gfortran dargestellt. Günstig ist hier noch die Option -O, die eine Optimierung des Codes (i.e. kürzere Laufzeit) bewirkt, mit -C werden array bounds checks zur Laufzeit des Programms durchgeführt. 1.4 Ein Beispiel Anhand eines Beispiels sollen jetzt einige Sprach-Elemente von Fortran eingeführt werden. Weiter unten wird alles genauer erläutert. Die fettgedruckten Wörter sind sind hierbei Schlüsselwörter (reservierte Wörter) der Programmiersprache und können z.B. nicht als 2 Variablennamen verwendet werden. Der einzugebende FORTRAN-Text lautet: Spalte 1-----7---------------------------------------------------------------72 program Wurzel ** Programm zur iterativen Berechnung der Quadratwurzel implicit none ** Variablen-Definitionen integer i real*8 a, epsilon, x neu, x alt ** Eingaben write(*,*) ’ Zahl ? ’ read (*,*) a ** Initialisierung x alt = a/2 epsilon = 1.0D-6 ** Iteration 10 continue x neu = (x alt + a/x alt)/2.0d0 if (abs(x neu - x alt) .gt. epsilon ) then x alt = x neu goto 10 end if 20 continue ** Ergebnis ausdrucken write (*,*) ’ Die Quadratwurzel von ’,a,’ ist ’,x neu ** Programm Ende Stop End Dieses Beispiel werde in wurzel.f abgespeichert. Dann kann es mit gfortran -o progx wurzel.f übersetzt werden; und mit progx gestartet werden. Es sollte mit Zahl ? antworten. Auf die Eingabe von ’2.0’ ergibt sich das Ergebnis: 3 Die Quadratwurzel von 2. ist 1.41421356 Die in diesem Beispiel (vielleicht selbsterklärenden) enthaltenen Fortran-Elemente werden im Folgenden genauer besprochen. Das continue statement bewirkt nur eine weitere Ausführung des Programms ohne jegliche Berechnung (siehe unter 6). 2 Programm-Struktur Die wesentliche Struktur eines Programms sieht also folgendermaßen aus: ** program Program Name Erstes Statement eines Programms (des Hauptprogramms) ** Typ-Definitionen Hier z.B. implicit none ** Variablen-Definitionen Hier z.B. real*8 a, epsilon, x neu, x alt ** Statements / Anweisungen Hier z.B. x neu = (x alt + a/x alt)/2.0d0 ** Input / Output (I/O) Hier z.B. write(*,*) ’ Zahl ? ’ read (*,*) a ** Programm Ende Stop End 2.1 Der Programm-Name Der Identifier program muss zu Beginn eines jeden Hauptprogramms stehen. Er kann entweder gross, klein oder auch gemischt (wie im Beispiel) geschrieben werden (s.u.). Der Programm Name, hier program name, darf laut Standard-F77 nur 6 Buchstaben/Zahlen lang sein, wobei diese all GROSS geschrieben werden mussten, und am Beginn ein Buchstabe (keine Zahl) stehen muss. Also ist z.B. PROG1 eine gültiger Programmname genäß Standard, aber nicht Prog1 oder PROGRAMM1 oder 2PROGRAMM. Heutzutage nehmen allerdings alle Compiler Namen an, die bis zu 31 Buchstaben lang sind, weswegen man sich hier keinerlei Einschränkungen hingeben sollte. WICHTIG ist hierbei, dass Groß- und Kleinschreibung NICHT unterschieden werden, als z.B. PROG1 das gleiche wie pRoG1 bezeichnen!! 4 2.2 Datentypen In jeder Programmiersprache gibt es bestimmte Datentypen, und mit den verschiedenen Variablen dieser Typen werden dann im Verlauf des Programms einige Rechenoperationen (Anweisungen) durchgeführt werden. Die wesentlichen 3 Datentypen von Fortran lauten: integer real double precision oder oder real*4 real*8 Der Typ integer bezeichnet Ganze Zahlen von −Nmin bis +Nmax , wobei der Wert von Nmin und Nmax je nach Platform anders aussehen kann. Typischerweise, bei einer internen Länge der Integer-Zahlen von 4 Byte, gilt Nmin = 231 und Nmax = 231 − 1. Einige Beispiele für ganze Zahlen sind: 1 0 -100 + 31 Der Typ real oder real*4 bezeichnet Reelle Zahlen mit einer internen Länge von 4 Bytes, d.h. einer sog. einfachen Genauigkeit (single precision). Damit können üblicherweise Zahlen mit einer Genauigkeit von 7 signifikanten Ziffern dargestellt werden. Der Bereich des Exponenten umfasst etwa ±38. Relle Zahlen werden z.B. geschrieben als: Vorzeichen, Ganze Zahl 1, Dezimalpunkt, Ganze Zahl 2, e oder E, Vorzeichen, Exponent Einige Beispiele: 7.375 .7375e+1 ! das Gleiche .07375 e 2 ! wiederum das Gleiche 73.75 E-1 ! und wiederum das Gleiche -7.375 ! das Negative Ohne ins Detail zu gehen, sieht man hieran, dass einige Teile wie der Exponent-Bereich (z.B. e+1) auch wegelassen werden können. Auch ist es mögliche zur Erhöhung der Lesbarkeit Leerstellen einzufügen (wie - 7.3 e +1). Der Exponent bezieht sich immer auf das Dezimalsystem, d.h. die Basis 10. Also bedeutet - 7.3 e +1 auf deutsch 7,3 mal 101 (=73). Die Exponentbezeichnung kann auch hier entweder groß ’E’ oder klein ’e’ geschrieben werden. Der Typ double precision oder real*8 bezeichnet ebenso Reelle Zahlen, aber mit einer internen Länge von 8 Bytes, der sog. doppelten Genauigkeit (eben der double precision). Damit können üblicherweise Zahlen 5 mit einer Genauigkeit von 15 signifikanten Ziffern dargestellt werden. Der Bereich des Exponenten umfasst hier etwa ±308. Zur Unterscheidung von real*4-Zahlen mit einer einfachen Genauigkeit müssen real*8-Zahlen mit der Exponent-Bezeichnung ’D’, oder ’d’ anstatt ’E’ oder ’e’ geschrieben werden. Der Typ real*8 sollte heutzutage für numerische Rechnungen mit reellen Zahlen die übliche Typ-Definition sein. Obiges Beispiel lautet also als real*8-Zahl: 7.375 D0 wobei hier D0 fur 100 = 1 steht. Die weiteren Datentypen, die nicht so wichtig für das erste Programmieren in Fortran sind, lauten: logical character complex Der Typ logical bezeichnet einen logischen Wert mit einem Wertebereich von .true. und .false., also wahr und falsch. NOTE: Die Punkte gehören einer logical-Größe dazu! Der Typ character bezeichnet Daten von Typ eines Wortes (strings). Beispiele sind: ’abcdef’ oder ’H2o’. character-Variablen werden mit (einfachen) Anführungsstrichen dargestellt. Hier werden Gross- und Kleinschreibung unterschieden, also ist ’abc’ nicht der gleiche Wert wie ’ABC’. Für unsere Zwecke sind character-Variablen zunächst nur bei der Ein- bzw. Ausgabe von Ergebnissen wichtig, dabei kommen wir i.a. ohne eine spezifische Deklaration aus. Will man ein Apostroph selbst darstellen muss man zwei Abführungsstriche nacheinander benutzen. Beispiel: ’It’’s a nice day!’ Der Typ complex bezeichnet komplexe Zahlen bestehend aus einem Zwei-Tupel von reellen Zahlen. Die Darstellung erfolgt durch zwei eingeklammerte real*4-Zahlen. z.B. hat (2.34, -1.5) einen Real-Teil von 2.34 und einen Imaginär-Teil von -1.5. 2.3 Variablen-Deklaration Zu Beginn eines Programm sollten alle Variablen, die im Lauf des Programm benutzt werden vorher definiert werden. Dies geschieht in Fortran durch: Typ-Identifier Variablen liste Hier bezeichnet der Typ-Identifier einen der obigen Daten-Typen (wie real*8 oder integer), wobei auch wieder Groß- oder Kleinschreibung gemischt werden kann, und die Variablen liste bezeichnet eine Liste von Variablennamen, die später im Code verwendet 6 werden sollen. Im ersten Beispiel. bedeutet also Real*8 a, epsilon, x neu, x alt dass die Variablen a, epsilon, x neu, x alt alle vom Typ real*8 sind. Für die Variablennamen gilt das gleiche wie für den Programmnamen: Bis 31 Buchstaben, groß und klein werden nicht unterschieden, die Einbindung eines Unterstrichs ’ ’ ist möglich. WICHTIG: In Fortran gilt die implizite Regelung, dass im Falle der Nicht-Deklaration von Variablen alle Variablen, die mit den Buchstaben i bis n im Alphabet beginnen vom Typ integer sind. Und weiterhin, dass alle von a bis h und o bis z vom Typ real*4 sind! Im obigen Programm-Beispiel wird diese Standard-Regelung ausgeschaltet durch implicit none was heißt, dass es keine implizite Regelung geben soll. Auf den ersten Blickt erscheint die Ausschaltung ein Nachteil zu sein. Nun ist es im anderen Fall allerdings möglich Variablen zu benutzen, die vorher nicht explixit definiert worden sind. Wenn man sich nun vertippen sollte bei der Programmerstellung, kann man möglicherweise undefinierte Variablen benutzen deren Werte wirklich undefinierbar (z.B. unendlich) sein können. Das Programm wird i.a. nicht abbrechen, sondern immer weiter rechnen und am Schluß einigen Unsinn produziert haben. Um dies zu Vermeiden kann nur dringend empfohlen werden, in allen Programmteilen zunächst implicit none zu setzen. Für die Unverbesserlichen: Das Wiedereinschalten, oder eine Umdefinition der impliziten Deklaration kann durch implicit integer (i-n) implicit real*8 (a-h, o-z) bewirkt werden. D.h. jetzt sind wieder alle Variablen von i bis n vom Typ integer und weiterhin alle von a bis h und o bis z vom Typ real*8. 3 3.1 Mathematische Berechnungen Grundrechenarten Zwischen den arithmetischen Variablen (integer und real) werden die üblichen Rechenoperationen Addition, Subtraktion, Multiplikation, Division, Exponentiation definiert. Die entsprechenden Symbole lauten +, -, *, /, ** und entsprechen den üblichen Bezeichnungen. Es gelten die üblichen Rechenregeln wie sie von der Schule bekannt sind, so geht Multiplizieren/Dividieren vor Addieren/Subtrahieren, und Exponentiation geht vor allen anderen. Klammerausdrücke werden zuerst ausgewertet. So wird durch i = 5+2**3*7 zuerst die Exponentiation (2 hoch 3 =8) ausgewertet dann multipliziert (8 mal 7 = 56) und dann die 5 addiert. Also wird durch diese Anweisung der Variablen i den Wert 61 zugewiesen. WICHTIG: Bei einer Division von zwei Ganzen integer-Zahlen geht der Rest verloren. Also wird durch: k = 5/2 7 k der Wert 2 zugewiesen. Als Exponent können auch Brüche verwendet werden. Es bedeutet: x = 2**(1./2.) √ dass x der Wert 2 (Wurzel aus 2) zugewiesen wird. NOTE: Hierbei sind die Dezimalpunkte im Exponenten lebenswichtig, da durch x = 2**(1/2) x der Wert 20 = 1 zugewiesen wird. Bei Operationen mit Variablen verschiedenen Typs (wie Real oder integer wird zunächst vor der mathematischen Operation eine Variable intern in einen anderen Typ verwandelt. Es bedeuten z.B. x = 2.0 * i wenn x vom Typ Real und i vom Typ integer ist, dass zuerst i auch in eine RealVariable umgeformt wird, und dann erst multipliziert wird. 3.2 Funktionen Neben diesen Grundrechenarten gibt es natürlich auch einige Funktionen, die bei Eingabe von Parametern Berechnungen ausführen und Ergebnisse eines bestimmten Datentyps ausgeben. Hier beschreiben wir zunächst die sog. Intrinsic Functions, die vorgefertigten, eingebauten Funktionen. Dei eigenen, durch den Benutzer definierten weiter unten bei den Unterprogrammen (Sektion, 7). Häufig benutzte eingebauten Funktionen sind abs min max sqrt sin cos tan atan exp log log10 Absolut Betrag Minimal Wert Maximal Wert Quadrat Wurzel Sinus Cosinus Tangens Arcus Tangens Exponential Fkt. Logarithmus (natürlicher) Zehner-Logarithmus Ein einfaches Beispiel kann so aussehen x = cos(pi/3.0) Womit x der Wert 0.5 zugewiesen wird, falls pi richtig definiert wurde. Generell hat eine Funktion immer einen bestimmten Datentyp. Viele der obigen Funktionen sind jedoch generic, d.h. der Datentyp des zurückgelierten Wertes hängt von der Eingabe ab. So könnten pi und x im obigen Beispiel entweder real*4 oder real*8 sein. Der Compiler prüft dies und die cos-Funktion liefert automatisch den richtigen Datentyp. 4 I/O Ein- und Ausgaben Die meisten Programme werden Input/Output (I/O) Operationen benutzen, damit Daten ausgetauscht und Ergebnisse analysiert werden können. Im Beispiel Programm wurzel.f 8 werden dazu ein print-statement write und ein read-statement, read eingeführt. Im einfachtsten Fall lauten diese: read (*,*) Eingabe Liste write (*,*) Ausgabe Liste Das erste der beiden Sternchen bedeutet hierbei, dass die Ein-/Ausgabe jeweils von/auf der/den Tastatur/Bildschirm erfolgen soll. Der zweite Stern bedeutet, das diese Formatfrei erfolgt. Formatfrei heisst hier, das zum Beispiel die Zahl 7.375 je nach eigenem gusto in all den Formaten, wie sie oben als Beispiel erwähnt wurden, eingegeben werden kann. Die Ein-/Ausgabeliste bezeichnet jeweils eine Variablen liste, in der die Variablen durch Kommata getrennt werden. Bei einer Eingabeliste schließt ein Enter die Eingabe ab. Es bedeutet z.B. integer i, kk real*8 y, Var read (*,*) i, y, Var, kk das hier 4 Werte (integer, real*8, real*8, integer) eingelesen werden sollen. Wird vor der Eingabe des letzten Wertes schon auf Enter gedrückt, wartet der Rechner auf weitere Eingaben. Eine falsche Eingabe (hier z.B. Buchstaben) führt üblicherweise zu einem Programmabbruch. Man versuche z.B. im obigen Wurzel-Programm den Wert ’abc’ einzugeben. Auch bei der Eingabe gilt, dass nur mit dem Exponent-Indentifier ’D’ bzw. ’d’ bezeichnete Zahlen real*8 sind. Ansonsten sind diese nur real*4 (und können ab der 8 Stelle beliebige Werte haben.) Bei der Eingabe von überschüßigen Werten werden zuviel eingebene ignoriert. 4.1 Umlenken der Ein- und Ausgaben Oft ist es nützlich, Daten in Dateien (files) zu schreiben, bzw. von solchen zu lesen. Dies erfolgt durch eine Erweiterung der obigen Befehle write und read. Vor deren Benutzung müssen jedoch die entsprechenden Dateien “geöffnet“ werden, was heißt, dass die Dateinamen dem Programm mitgeteilt werden müssen. Dies geschieht durch den openBefehl, dessen einfachste Syntax, hier über zwei Eingabezeilen (Spalte 72!) verteilt, lautet: open (iunit, file=file name, status= ’unknown’ & ,form=’formatted’) Weitere Argumente des Open-Statements sind in entsprechenden Fortran-Büchern zu finden. Hier bedeuten: iunit Eine ganze Zahl zwischen 9-99, die dem File intern im Programm als Kanalnummer zugeordnet wird. NOTE: Die Nummer 5 bezeichnet Standardmässig den Bildschirm (bei der Ausgabe), und die Nummer 6 die Tastatur (bei der Eingabe). file name Der Name einer Datei, eingeben durch einen String status= ’unknown’ dass es beim Öffnen der Datei egal ist, ob diese bereits existiert oder nicht. Existiert eine Datei des Names file name bereits und wird diese zur Ausgabe von Daten verwendet, so 9 werden diese überschrieben. Weitere status-Möglichkeiten sind ’old’ - die Datei existiert bereits, bzw. ’new’ - sie wird neu angelegt. form=’formatted’ dass die Daten in diesem File, normale (lesbare) ASCI-Daten sind. Im Fall von form=’unformatted’ liegen die Daten nur in Maschinenlesbarer Form, im sog. binären Format vor. Damit lautet: OPEN(16,FILE=’oh1d’,status=’unknown’,FORM=’FORMATTED’) dass intern dem File ’oh1d’ die Nummer 16 zugewiesen wird, dieser bereits existieren kann, oder auch nicht, und die Daten in menschen-lesbarer Form vorliegen. NOTE: Beim Filenamen wird (wie unter Linux/Unix üblich) zwischen Groß- und Kleinschreibung unterschieden. Das Lesen und Schreiben in eine solche Datei geschieht jetzt durch (die Variablen-liste wurde unterdrückt) read ([UNIT=]iunit, [FMT=]ifmt, IOSTAT=ios, ERR=ierr) write([UNIT=]iunit, [FMT=]ifmt, IOSTAT=ios, ERR=ierr) wobei iunit die entsprechende Kanalnummer bezeichnet und, ifmt ein Format (s.u.), in dem die Daten gedruckt (gelesen) werden sollen, ios ist ein I/O Status-Identifier (integer-Variable), welche bei erfolgreicher Aktion auf Null gesetzt wird und sonst auf einen Wert ungleich Null. ierr bezeichnet ein Label, das angesprungen wird, falls ein Fehler auftritt. Die beiden letzten Optionen können auch weggelassen werden (z.B. wie bei write(*,*)) wenn man nicht genau an einer Fehlerbehandlung interessiert ist, oder sowieso alles glatt läuft. Die Begriffe in eckigen Klammern können auch weggelassen werden. Es reicht also: read (iunit, ifmt) write(iunit, ifmt) Wir hatten schon (im Wurzelbeispiel) gesehen, dass der einfachste Identifier für die Kanalnummer und das Format der Stern ’*’ ist. Somit können read (*, *) write(*, *) als Spezialfall der allgemeinen Schreibe-/Lesekommandos betrachtet werden. Nach dem alle Daten in einen File geschrieben/gelesen worden sind, ist es günstig, diesen wieder zu schliessen, was mit dem close-statement geschieht close (iunit) Der file wird also einfach durch die Angabe der entsprechenden Kanalnummer (iunit) wieder geschlossen. 4.2 Formate Will man Daten nicht vollständig format-frei, sondern z.B. in einer Tabelle, in der alle Daten untereinander stehen sollen, ausgeben so ist man auf ein detailliertes Format angewiesen. Übliche Handbücher für Programmiersprachen (C, FTN, etc.) verwenden teilweise viele, viele Seiten auf eine genaue Beschreibung aller möglichen Formate. 10 Wir vereinfachen, indem wir nur die allerwichtigsten (der ca. 1000 Optionen) beschreiben. Die Syntax eines Ausdruck-Statements lautet: open (16, file= ’output’) write(16, 1000) Variablen Liste 1000 format format code Hier ist also die Zahl 1000 als Label-Nummer (ifmt) definiert worden. Das Format-Label kann überall im Programm-Text stehen, und auch von verschiedenen write-Anweisungen benutzt werden. Die Ausgabe erfolgt hier in den File ’output’ mit der internen Kanalnummer iunit=16. Wird im obigen Beispiel vergessen eine Datei mit iunit=16 zu öffnen wird in eine Datei geschrieben, deren Namen Platform abhängig sein kann (übliche Bezeichnungen sind z.B. ’fort.16’ oder ’ftn16’). Der format code beinhaltet nun genau die Anweisungen, wie die Variablen der Variablen Liste gedruckt werden sollen. Betrachten wir zunächst ein Beispiel x = 0.025 write(16, 1000) ’x=’,x 1000 format (A,E8.1) Hier wird die real-Variablen x, die den Wert 0.025 habe, in der sog. Exponenten-Notation ausgedruckt, wobei En.m bedeutet, dass die Darstellung insgesamt (einschliesslich) Vorzeichen und Exponent, n Stellen (hier 8) in Anspruch nimmt, wobei die Mantisse durch m Stellen ausgedrückt wird. Bei der Exponenten-Notation werden keine Vorkommastellen (oder nur eine Null, je nach Platform) ausgedruckt. Der Exponent nimmt bei real*4-Zahlen immer mindestens 4 Stellen ein E±nn. Die Qualifier n und m können auch ganz weggelassen, also nur E verwendet werden, dann wird in einem Standard-Format gedruckt. Sollen real*8-Variablen in einem grossen Wertebereich ausgedruckt werden, so kann optional statt E auch D verwendet werden. Der Wert A gibt an, dass hier ein String (hier ’x=’) gedruckt werden soll. Auch bei A kann noch die Länge des Strings durch An bezeichnet werden, wobei n hier wieder die Gesamtlänge des Ausdrucks n Buchstaben bezeichnet. Die obige Anweisung produziert also den Ausdruck x= 0.3E-01 NOTE: Hier wird stark gerundet, weil zu wenig Gesamtstellen angegeben wurden. Die Rundung erfolgt in Fortran nach den Standardregeln: 0-4 nach unten, 5-9 nach oben. Die wichtigsten Format-Buchstaben sind A E D I X Text-String Real-Zahlen, exponent notation Double-Zahlen, exponent notation Integer Zahlen Horizontale Verschiebung Eine Ganze Zahl nach diesen Format-Buchstaben gibt immer die gewünschte Länge des Ausdrucks an. Z.B. bedeutet I8, dass eine ganze Zahl mit 8 Stellen ausgedruckt wird. Eine Zahl vor dem Format-Buchstaben gibt die Anzahl der Wiederholungen an mit dem dieses Format verwendet werden soll. So bedeutet 8X, dass ein Leerraum von 8 Stellen erzeugt wird. Hier ist einiges an Experimentiererei angesagt. Laut Standard-F77 müssen 11 alle Format-Buchstaben Großbuchstabig sein, aber die meisten (alle) Compiler nehmen auch hier kleine Buchstaben an. Zum Ausdrucken von Feldern siehe Sek.8.2. 5 Entscheidungen Ein wichtiger Teil von Programmen sind bedingte Anweisungen, die ausgeführt werden je nach vorliegen einer bestimmten (logischen) Bedingung. Die wichtigste EntscheidungsAnweisung ist das if-statement. In der einfachsten Form lautet dies: if (Logischer Ausdruck) ausführbare Anweisung Zum Beispiel if (x .lt. 0) x = -x Damit wird x immer sein positiver Teil zugewiesen (ist also das Gleiche wie x = abs(x). Als logische Ausdrücke können, wie oben erläutert nur zwei Ergebnisse .true. und .false.. Sie können u.a. aus dem Vergleich arithmetischer Ausdrücke (wie Im Beispiel) erzeugt werden. Hierbei gilt: .lt. .le. .gt. .ge. .eq. .ne. < <= > >= = /= kleiner als (lower then) kleiner gleich (lower equal) größer als (greater than) größer gleich (greater equal) gleich (equal) ungleich (not equal) Logische Ausdrücke können falls nötig mit Hilfe von logischen Opratoren mit anderen logischen Ausdrücken verknüpft werden. if ( (x .lt. 0) .and. (y .gt. 0) ) z = x * y Hier gibt es .and., .or. und .not., also logisches und, oder und nicht. Falls mehr als ein statement bei ein if-Abfrage ausgeführt werden soll, muss die Anweisung lauten if (Logischer Ausdruck) then ausführbare Anweisungen end if Hier wird der vollständige if-Ausdruck durch if und end if eingeklammert, siehe obiges Beispiel. Die allgemeinste Form lautet if (Logischer Ausdruck) then ausführbare Anweisungen else if (Logischer Ausdruck) then ausführbare Anweisungen : : else end if 12 Dabei erfolgt die Abarbeitung von oben nach unten, bis eine Bedingung als wahr erkannt worden ist. Die Anweisungen werden abgearbeitet und nach dem end if wird fortgesetzt. Die Schlüsselwörter else if und end if können auch zusammengeschrieben werden, wie elseif und endif. if-Ausdrücke können auch geschachtelt werden. z.B.: if (x .GT. 0) then if (x .GE. y) then write(*,*) ’x ist positiv und x >= y’ else write(*,*) ’x ist positiv aber x < y’ endif elseif (x .LT. 0) then write(*,*) ’x ist negativ’ else write(*,*) ’x ist Null’ endif Zur besseren Lesbarkeit empfiehlt es sich hier, die Befehle entsprechend einzurücken. 6 Schleifen Oft sollen viele Befehle hintereinander ausgeführt werden. Hier unterscheidet man manchmal zwischen for-loops, while-loops, und until-loops, Begriffe, die von einigen modernen Programmiersprachen wie Pascal erkannt werden. FTN77 hat nur eine Schleifenstruktur, die do-Loop. Die beiden anderen Arten kann man sich allerdings leicht konstruieren. 6.0.1 DO-Schleifen Will man z.B. die Summe aller Zahlen von 1 bis N berechnen, so muß man dies in einer Schleife tun: N = 100 Isum = 0 Do i=1, N isum = isum + i end do Alle Variablen seien hier vom Typ integer. Am Ende der Schleife, die bei Do beginnt und bei end do endet, ist der Wert von isum gleich der Summe der Zahlen von 1 bis 100. Die allgemeine Form einer Do-Loop lautet in Fortran Do var = expr1, expr2, expr3 Anweisungen end do wobei die Schleifenvariable (oder der Schleifenindex) ivar vom Typ integer sein muß. expr1 bezeichnet den Anfangswert, expr2 den Endwert und expr3 die Schrittweite des Scheleifenindex. Der Schleifenindex sollte innerhalb der Do-Loop niemals geändert werden. Der zweite Index expr2 kann auch kleiner als der erste sein, dann ist expr3 negativ. Wird expr3 wegegelassen, dann ist das Inkrement für die Schleife gleich Eins (expr3 =1). 13 NOTE: Die obige Erläuterung einer Do-Loop entspricht nicht ganz dem alten F77Standard, obwohl fast alle (alle?) Compiler diese Erweiterung mittlerweile akzeptieren. In Standard FTN77 lautet eine Schleife: Do label var = expr1, expr2, expr3 Anweisungen label continue Dabei steht das label für einen Zeiger, der die Do-Loop einschachtelt. Alle Anweisungen zwischen dem Do-Statement und dem continue Statement gehören zur Schleife. Die Anweisung continue bewirkt nichts weiter, als dass der Programmablauf hier fortgesetzt wird. 6.0.2 While/Until-Schleifen Die inituitive Art einer while-Schleife wäre eine Konstruktion der Art: while (logischer Ausdruck) do Statements end while In Fortran wird eine solche Schleife durch eine if-Abfrage und einem goto-Sprungbefehl realisiert. Dies ist im Code-Beispiel zur Wurzelberechnung auch gemacht worden. Damit kann eine while-Schleife in FTN77 lauten label if (logischer Ausdruck) then Statements endif Das nächste Beispiel berechnet und druckt alle Potenzen von 2 aus, die kleiner als 100 sind. 10 integer n n = 1 if (n .le. 100) then n = 2*n write(*,*) n goto 10 end if Zur besseren Lesbarkeit war das Label im Wurzelbeispiel vor die if-Abfrage gezogen. 7 Unterprogramme Oft ist es nicht sinnvoll, alle notwendigen Statements eines Programms nur in einen File zu schreiben. Dann verwendet man sog. Unterprogramme subroutines oder eigene Funktionen, um eine bessere Übersichtlichkeit des Gesamtprogramms zu gewährleisten. Diese einzelnen Programmteile können auch in verschiedenen Files stehen. Sie werden über den Compiler (Linker) zu einem Programm zusammengebunden. 14 7.1 Subroutines Die Struktur eines Unterprogramms sieht ähnelt zunächst sehr derjenigen des Hauptprogramms subroutine sub name ( Variablen Liste ) Deklarationen, Statements, Anweisungen, I/O return end Es wird hier also das Schlüsselwort subroutine zur Bezeichnung eines Unterprogramms verwendet. Der Name der Routine, mit dem sie innerhalb des Programms oder auch von anderen Subroutines aufgerufen werden kann lautet hier sub name. Durch die Variablenliste werden der Subroutine nötigen Parameter mitgeteilt. Diese Variablen (beliebigen Typs) müssen im aufrufenden Hauptprogram und der Subroutine mit gleichem Datentyp definiert werden, ansonsten können unvorhersehbare Ergebnisse produziert werden. NOTE: Die Werte der Argumente einer subroutine, den Variablen in der Liste, können innerhalb der Routine verändert werden, und diese veränderten Werte sind dem aufrufenden Programm sichtbar (call-by-reference). Dies kommt dadurch, dass dem Unterprogramm keine Kopien der Variablen mitgegeben werden, sondern direkt die internen SpeicherAdressen (Pointer) der entsprechenden Variablen. Ein Beispiel: subroutine iswap (a, b) ** Argumente: integer a, b ** Lokale Variablen integer tmp tmp = a a = b b = tmp return end Wichtig hierbei ist, dass es zwei Arten von Variablen gibt. Die Argumente, (a, b) die auch im aufrufenden Programm sichbar sind, und die lokalen Variablen tmp, die nur in der Subroutine “gesehen“ werden können. Die Namen können also mit Namen in anderen Routinen übereinstimmen und nur intern durch den Compiler unterschieden werden. Das Aufrufende Programm könnte so aussehen: program callex integer m, n C m = 1 n = 1 call iswap (m,n) write (*,*) m, n stop end Der Ausdruck ist hier, wie erwartet ”2 1”. Das Beispiel illustriert gleichzeitig den Aufruf einer Subroutine durch das call-Statement. 15 7.2 Funktionen Als Ergnazung zu den subroutines bietet Fortran noch dei Möglichkeit zu Definition eigener Funktionen. Hier sieht die allgemeine Deklaration so aus: Type def function func name ( Variablen Liste ) Deklarationen, Statements, Anweisungen, I/O func name = result return end Der wesentliche Unterschied zur Subroutine-Deklaration liegt darin, dass hier ein Datentyp durch Type def der Funktion definiert werden muss. Die Funktion erhält innerhalb ein Ergebnis (Funktionswert) zugewiesen. Der Aufruf erfolgt genauso, wie bei den intrinsichen Funktionen, der Datentyp sollte allerdings im aufrufenden Programmteil genau definiert worden sein. Gegeben werde als Beispiel die Berechnung der Fakultät einer Ganzen Zahl (N!). integer function nfac (n) ** Das Argument integer n ** Lokale Variable integer i, ni ** Berechnung ni = 1 do i=1, n ni = ni * i end do ** Wert zuweisen nfac = ni return end Das aufrufende Programm könnte so aussehen program tstnfac C C Eine Uebung zum Testen Fakultaetsfunktion C integer n, nfac ! Der Datentyp der Funktion wird hier definiert 10 continue write(*,*) ’ N eingeben: ’ read (*,*) n if (n.gt.0) then write(*,*) n, ’ Fakultaet ist: ’, nfac(n) goto 10 endif C Schleifen-Ende stop end Für die Namen der Subroutines und Funktionen gilt das Gleiche wie für Programmnamen. 16 7.3 COMMON-Bereiche Wir hatten gesehen, dass es innerhalb von Funktionen und Subroutines lokale Variablen gibt, die nur innerhalb der Routine gelten. Gleichzeitig gibt es die Argumentlisten deren Variablen, innerhalb als auch in der aufrufenden Routine gesehen werden, allerdings werden diese Variablen nicht von möglichern anderen Routinen gesehen. Zusätzlich gibt es nun die Möglichkeit einer globalen Variablendefinition die von allen Routinen gesehen werden kann. Dies geschieht in Fortran durch die Definition von COMMONBereichen, d.h. gemeinsamen Datenbereichen. Beispiel: Nehmen wir an, dass viele Programmteile die Werte von alpha and beta benötigen: program main Einige Deklarationen real alpha, beta common /coeff/ alpha, beta Statements stop end subroutine sub1 (einige Argmente) Deklarationen der Argumente real alpha, beta common /coeff/ alpha, beta Statements return end subroutine sub2 (einige Argmente) Deklarationen der Argumente real alpha, beta common /coeff/ alpha, beta Statements return end Der Name des common-Blocks in allen Routinen muss der gleiche sein, und die Datentypen der Daten sollten darin immer identisch sein. Günstig ist zur Vermeidung von Mißverständnissen immer die gleiche Bezeichnung dieser Variablen in allen Unterprogrammen. Die Zahl der Variablen in einem Block kann beliebig lang sein. 8 Felder Will man größere Datenmengen (Vektoren, Matrizen) verarbeiten, so ist es sinnvoll für solche Daten Felder arrays zu definieren. Diese können in einer oder auch mehreren Dimensionen definiert werden. Ein Beispiel: program fibonacci ** Berechnet die ersten 15 Fibonacci Zahlen 17 integer i integer f(15) f(1) = 1 f(2) = 1 do i=3, 15 f(i) = f(i-1) + f(i-2) end do write(*,*) f stop end Hier wird das eindimensionale Array f (15) dazu verwendet, die berechneten FibonacciZahlen zu speichern. Das Feld wird als integer-Feld der Länge 15 definiert. Der Index läuft also von 1 bis 15. Der Zugriff auf das i − te Element erfolgt ganz einfach wie angegeben, durch f(i). Durch das write-Statement wird das ganze Array (15 Zahlen) auf einmal ausgedruckt. Die Indizes von eindimensionalen Felder müssen nicht unbedingt bei 1 beginnnen. Die folgenden Definitionen sind also möglich: real a, b dimension a(0:20), b(-125:256) Jedes einzelne Element eines Feldes kann als separate Variable betrachtet werden, wobei die einzelnen Elemente der Felder physikalisch im Speicher memory des Computers direkt hintereinander stehen. Bei den letzten Deklarationen wurde das Schlüsselwort dimension verwendet. Dies kann jedoch, wie anhand des oberen Fibonacci-Beispiels gezeigt, weggelassen werden. Bei Verwendung des dimension statements muss der Datentyp (hier real) vorher deklariert werden. Der Datentyp von Arrays kann im Prinzip beliebig sein: integer i(10) logical aa(0:15) double precision x(100) NOTE: Ein häufiger Fehler bei Programmieren ist der Zugriff auf Feldelemente, die außerhalb des definierten Bereichs liegen, z.B. oben auf x(101). Dies führt typischerweise zu keiner Fehlermeldung und auch nicht zum Abbruch des Programmms. Es gibt aber die Compiler-Option -C (Minux gross C), die zur Laufzeit des Programms die Feldgrenzen abprüft (array bounds check. (siehe auch 1.3). 8.1 Mehrdimensionale Neben den eindimensionalen Feldern können auch Felder mit beliebig vielen Dimensionen erzeugt werden. Dabei treten zwei dimensionen (z.B. bei Matrizen), aber auch 3-dimensionale Felder (Mehrdimensionale Hydrodynamik) am häufigsten auf. Die Deklaration real a(3,5) definiert ein 2-dimensionales Feld mit 3*5=15 Zahlen. Der erste Index bezeichnet die Zeile, der zweite die Spalte! 18 Auch hier können die Indizes über einen beliebigen Bereich laufen. Die allgemeine Deklaratioon lautet: name (unterer index1 : oberer index1 & ,unterer index2 : oberer index2) Es ist üblich in Fortran, Felder zu definieren, die grösser sind als der Bereich, der eigentlich benötigt wird. Dies liegt daran, dass Fortran-77 keine dynamische Speicherverwaltung kennt. Ein Beispiel: real A(3,5) integer i,j C C C 10 20 Nur der obere 3x3 Bereich des Feldes wird benutzt werden do 20 j = 1, 3 do 10 i = 1, 3 a(i,j) = real(i)/real(j) continue continue Zur Illustration haben wir hier mit dem continue-Statement gearbeitet. Die Funktion real(i) wandelt eine integer-Variable in eine real-Größe um. Hier werder also nur die neun Elemente A(1 : 3, 1 : 3) benutzt, und die sechs Elemente A(1 : 3, 4 : 5) nicht. Diese Elemente können durch den Compiler irgendeinen Wert haben, nicht unbedingt Null. 8.2 Das Ausdrucken von Feldern Daxs Ausdrucken von ganzen Feldern kann bei Einzel-Ausdrck etwas mühselig sein. Hier bieten sich die Möglichkeiten einer Do-Loop oder einer impliziten Druck-Schleife an: Ein Beispiel aus einer Hydrodynamik-Rechnung. real*8 x(100), v(100), rho(100) ** Hier bezeichnen x : der Ort ** v : die Geschwindigkeit ** rho: die Dichte Statements ** Ausdrucken ** Durch Do Loop do i=1,100 write(16,1001) i, x(i), v(i), rho(i) end do 1001 format(1x, i6, 3(1x, E13.6) ) In der Schleife werden jeweils in einer Zeile die Werte i, x(i), v(i), rho(i) nebeneinander gedruckt durch das angegeben format (label 1001). Bei der Format-Anweisung wurde von der Wiederholungsmöglichkeit der einzelnen Formate Gebrauch gemacht: 3(1x, E13.6) C heisst soviel wie 1x, e13.6, 1x, e13.6, 1x, e13.6 19 Der implizite Ausdruck kann folgendermaßen aussehen write(16,1001) (i, x(i), v(i), rho(i),i = 1, 100) die Do-Loop ist also schon implizit in der write-Anweisung enthalten. 20