FORTRAN: 1. FORTRAN

HANDLEIDING FORTRAN

HOOFDSTUK 1: FORTRAN


1.8 Input/output

Het is in computerprogramma's vaak nodig data weg te schrijven naar secundair geheugen (disk of tape), en later weer in te lezen (tijdens executie van hetzelfde programma of een ander programma). Uiteraard is het ook meestal nodig data van het toetsenbord in te lezen en naar het scherm te schrijven. Printen is ook een vorm van uitvoer. We zullen nu uiteenzetten welke mogelijkheden FORTRAN biedt op het gebied van invoer en uitvoer (input en output of kortweg I/O).
De direct access en asynchrone mogelijkheden van FORTRAN zullen onbesproken blijven, we zullen alleen sequential I/O bespreken.

We onderscheiden twee soorten I/O:

1.8.1 Ongeformatteerde I/O

Hoewel deze vorm van I/O al even aan de orde geweest is, geven we volledigheidshalve toch even een beknopt overzicht. De drie elementaire FORTRAN I/O statements zijn READ, WRITE en REWIND. De syntax is:
        WRITE (UNIT=nn) A, B, C, ...
        READ (UNIT=nn) A, B, C, ...
        REWIND nn
Hier is ``nn'' een integer expressie die het unit nummer geeft
        0 <= nn <= 99
De variabelen A, B, C, ... kunnen variabelen, array elementen, hele arrays, of ``implied DO lists'' (zie later) zijn. Ieder WRITE statement definieert een record. Records worden achter elkaar (sequentieel) in een file geschreven. Als de file volledig geschreven is kan hij met een REWIND teruggespoeld worden naar het begin en dan kan de file gelezen worden met READ statements. Een nieuwe file kan een DOS naam gegeven worden door vóór executie van het programma het DOS kommando SET te geven. Bijvoorbeeld voor unit 1:
        SET 1=TEST.DAT
Als men geen SET doet wordt door WATFOR77 een file FTnnF001 aangemaakt, waar ``nn'' het unit nummer is.

Voorbeeld:

        DIMENSION A(100), B(100)

  C     INITIALISEER ELEMENTEN VAN ARRAY A  EN B.

        DO 10 I=1,100
           A(I) = I
           B(I) = 0.E0
     10 CONTINUE

  C     SCHRIJF RECORDS VAN TWEE ELEMENTEN ELK.

        DO 20 I=1,100,2
           WRITE (UNIT=1) A(I), A(I+1)
     20 CONTINUE

  C     GA TERUG NAAR BEGIN FILE

        REWIND 1

  C     LEES RECORDS TERUG IN ARRAY B

        DO 30 I=1,100,2
           READ (UNIT=1) B(I), B(I+1)
     30 CONTINUE

  C     SCHRIJF WAT VAN B NAAR SCHERM TER CONTROLE
  C     (UNIT 6 IS GEWOONLIJK SCHERM)

        DO 40 I=1,100,10
           WRITE (UNIT=6, FMT=*) B(I)
     40 CONTINUE
  C
        END
Omdat er 50 WRITE statements uitgevoerd zijn staan er dus na uitvoering van bovenstaande programma 50 records op de file verbonden met unit 1. De lengtes van de achtereenvolgende records worden bepaald door het aantal variabelen in ieder WRITE statement (in bovenstaande voorbeeld zijn alle records acht bytes lang).

De records mogen van verschillende lengte zijn, maar dan wordt het teruglezen lastiger. Er zijn drie mogelijkheden:

  1. We lezen minder bytes terug dan in het record staat. FORTRAN leest wat verwacht wordt en springt vervolgens naar het einde van het record. Deze mogelijkheid kan gebruikt worden om records over te slaan. Door een READ zonder variabele wordt het hele record geskipt.
  2. We lezen even veel bytes als er op het record staan (zie vorige voorbeeld).
  3. We proberen meer te lezen dan op het record staat, dit geeft een foutmelding.
Zoals we al gezien hebben in sectie 1.3 heeft het READ statement een handige mogelijkheid om tot het einde van een file (niet record!) te lezen. Syntax:
      READ (UNIT=nn, END=lll) A, B, C, ....
Als het einde van het file bereikt wordt, springt het programma naar het statement met label ``lll''.

Voorbeeld:

        DIMENSION A(100), B(100)

  C     INITIALISEER ARRAY A

        DO 10 I=1,100
           A(I) = I
     10 CONTINUE

  C     SCHRIJF RECORDS VAN VARIABELE LENGTE.

        WRITE (UNIT=99) A(1)
        WRITE (UNIT=99) A(2), A(10)
        WRITE (UNIT=99) A(3), A(4), A(7)
        WRITE (UNIT=99) A(4), A(5), A(8), A(9)
        WRITE (UNIT=99) A(5)

  C     GA TERUG NAAR BEGIN FILE

        REWIND 99

  C     LEES EERSTE 4 BYTES VAN ELK RECORD TERUG IN ARRAY B

        I = 0
     20 I = I+1
          READ (UNIT=99, END=30) B(I)
        GO TO 20
  C
     30 N = I-1
        DO 40 I=1,N
           WRITE (UNIT=6, FMT=*) I, B(I)
     40 CONTINUE
  C
        END
In de READ loop worden in totaal vijf records gelezen, omdat er eerder vijf weggeschreven zijn. In de eerste leesopdracht wordt precies het hele record gelezen, in de tweede worden de eerste vier bytes van het record gelezen en de tweede vier bytes geskipt, in de derde READ worden acht bytes geskipt, enz. Men kan proberen om afwisselend in een sequentiële file te lezen en te schrijven, maar zodra men een record schrijft in een bestaande file zijn alle records er achter verloren. Men moet dus altijd aan het einde van de file toevoegen.

1.8.2 Geformatteerde I/O

De machinerepresentatie van FORTRAN variabelen is niet voor menselijke consumptie bedoeld, en bovendien kunnen printer en beeldscherm deze representatie ook niet weergeven. De meeste printers en schermen kunnen alleen zg. ASCII (American Standard Code for Information Interchange) code weergeven. Het omzetten van getallen van interne representatie naar ASCII representatie en omgekeerd heet formattering. Om een voorbeeld te geven: de integer 1 wordt intern in de computer gerepresenteerd door 32 bits waarvan de eerste 31 bits 0 zijn en de meeste rechtse een 1 is, terwijl het character 1 in ASCII gerepresenteerd wordt door één byte met de acht bits gelijk aan 00110001 (ASCII 49).

In FORTRAN heeft de programmeur de mogelijkheid het formatteringsproces te sturen. Dit wordt nu behandeld.

WRITE, READ en REWIND zijn weer de elementaire I/O statements. Syntax:

        WRITE (UNIT=nn, FMT=lll) A, B, C, ...
        READ (UNIT=nn, FMT=lll, END=kkk) A, B, C, ...
        REWIND nn
    lll FORMAT ( .... )
Hierin is ``nn'' het unit nummer, A, B, C, ... zijn FORTRAN variabelen (inclusief arrays, implied DO lists en constanten) en ``kkk'' is het label van een statement waarheen gesprongen wordt als de READ voorbij het einde van het file probeert te lezen. Nieuw is ``FMT=lll'' in het READ en WRITE statement. In het algemeen is ``lll'' het label van een FORMAT statement.

Eerder hebben we gezien dat ``lll'' ook een ster ``*'' kan zijn, in welk geval FORTRAN zelf uitmaakt op welke manier A, B, C, ... geformatteerd zullen worden. Het gebruik van dit standaard format (list-directed I/O) heeft als nadeel dat het moeilijk is om nette output (tabellen enz.) te schrijven. Hiervoor is het nodig om iets van formattering te weten. In het verleden werden data ook vaak geformatteerd ingelezen. Dit is vrij gebruikersonvriendelijk, en daarom hebben de meeste programma's tegenwoordig manieren om dit te vermijden.

1.8.3 Het FORMAT statement

We bespreken alleen het schrijven van de drie belangrijkste data typen in FORTRAN: integer, real en character. Deze vereisen een verschillend FORMAT.

Het lezen en schrijven van een integer gaat met formatcode ``In'', waarin ``n'' een geheel getal is dat het aantal cijfers aangeeft dat ingelezen of geschreven wordt. Bijvoorbeeld:

        WRITE (UNIT=5, FMT=500) INT
    500 FORMAT (I5)
FORTRAN schrijft de variabele INT rechts aangelijnd in de eerste vijf posities van de regel. Dus voor de volgende waarden van INT krijgen we
                             12345                                     12345
      INT = 1        --->        1              INT = -1       --->       -1
      INT = 12       --->       12              INT = -12      --->      -12
      INT = 123      --->      123              INT = -123     --->     -123
      INT = 12345    --->    12345              INT = -1234    --->    -1234
      INT = 123456   --->    *****              INT = -12345   --->    *****
Als de waarde van INT niet in het format past (in dit voorbeeld als INT > 99999 of INT < -9999) krijgen we even zoveel sterren te zien als het veld breed is.

In het algemeen is het verstandig de eerste positie van een output record blank te laten (spatie in positie 1), omdat lijnprinters dat eerste character als een besturingscharacter (het zg. carriage control character) interpreteren. Met dit eerste character kan men een lijnprinter bijvoorbeeld lege pagina's laten genereren of stil laten staan (waardoor regels over elkaar gedrukt worden). Het teken ``space'' (ASCII 32, de spatie) in de eerste positie vertelt de printer naar de volgende regel te gaan.

Formatcode ``nX'' geeft aan dat er ``n'' spaties in het record staan (bij invoer) of komen (bij uitvoer). Deze code kan gebruikt worden om uitvoer overzichtelijk te presenteren.

Voor character data gebruikt men formatkode ``An'' (A staat voor alphanumeric).

Voorbeeld:

        WRITE (UNIT=6, FMT=600) 'AAP NOOT MIES'
    600 FORMAT (1X, A13)
Dit schrijft de tekst voorafgegaan door een blank naar unit 6 (meestal de printer of het scherm).

Nu volgt een voorbeeld van een geheel programma met diverse geformatteerde I/O statements. In dit programma laten we de keywords UNIT en FMT weg in I/O statements. Dit is toegestaan, mits we beide tegelijk weglaten.

        DIMENSION INT(10)
        CHARACTER*35 ERROR

        ERROR = 'TE VEEL VARIABELEN, PROGRAMMA STOPT'
        IA = 10

        WRITE (6, 600) 'VOER GEHELE GETALLEN IN'
    600 FORMAT (1X, A23)

  C     READ INTEGERS VAN TOETSENBORD TOT 'END OF FILE'

        I = 0
     10 CONTINUE
           I = I + 1
           READ (5, 510, END=20) INT(I)
    510    FORMAT (I5)

  C        LEES ALLEEN VOLGENDE GETAL ALS WE NIET UIT ARRAY LOPEN

           IF (I .GE. IA) THEN
              WRITE (6, 610) ERROR
    610       FORMAT (1X, A35)
              STOP
           ENDIF
        GOTO 10

  C     KLAAR MET LEZEN, GA SCHRIJVEN

     20 CONTINUE
        N = I - 1
   
        WRITE (6, 620) 'AANTAL INGELEZEN GEGEVENS', N
    620 FORMAT (1X, A21, 4X, I3)
  C
        DO 30 I=1,N
           WRITE (6, 630) INT(I)
    630    FORMAT (1X, I8)
     30 CONTINUE
  C
        END
Een FORMAT statement mag character konstanten bevatten. In het algemeen zal men dit liever gebruiken dan het ``An'' format, omdat men dan niet hoeft te tellen. De volgende twee voorbeelden geven exakt dezelfde uitvoer:
        WRITE (6, 610) 'AANTAL INGELEZEN DATA', N
    610 FORMAT (1X, A21, 4X, I3)
en:
        WRITE (6, 610) N
    610 FORMAT (1X, 'AANTAL INGELEZEN DATA', 4X, I3)
Voor de uitvoer van floating point data (reële getallen met drijvende komma) bestaan twee formatmogelijkheden. De eerste mogelijkheid gebruikt men in het algemeen als men niet weet hoe groot de getallen zijn, of als ze erg ver in absolute waarde van 1 liggen. Dit format heeft de vorm ``El.d'', waarin ``l'' de lengte van het veld is en ``d'' aantal decimale cijfers (cijfers achter de punt). Dit format geeft bij uitvoer een grondtal en een macht van 10, zodat het grondtal tussen .1 en .9 ligt. Bijvoorbeeld voor de dubbele precisie variabele A = -0.123456789012345D-3 = -0.000123456789012345 krijgen we de volgende output:
                      12345678901234567890
   
       E13.6   --->   -0.123457E-03
       E13.7   --->   -.1234568E-03
       E13.8   --->   *************
       E20.1   --->               -0.1E-03
       E20.9   --->       -0.123456789E-03
Merk op:

Als de absolute waarden van de floating point getallen niet te ver van 1 liggen, is ``Fl.d'' een handig format. Hierin is ``l'' weer de lengte van het veld, en ``d'' het aantal cijfers achter de punt. In dit geval krijgen we geen macht van 10 te zien en hoeven dus niet de vier characters voor de exponent te reserveren. Stel A = 123.456789012345D0, dan krijgen we de volgende output:

                      12345678901234567890
 
       F13.6   --->      123.456789
       F13.7   --->     123.4567890
       F13.10  --->   *************
       F20.12  --->       123.456789012345
       F10.6   --->   123.456789
De invoer van floating point getallen heeft erg veel vrijheid. Men kan binnen het FORTRAN programma in het format statement ``Fl.d'' hebben, en in de invoer toch een exponent, en ook omgekeerd: als het format ``El.d'' is, hoeft in de input toch geen exponent te staan. In het algemeen zal men in de invoer de punt expliciet meegeven, hoewel dit niet hoeft.

1.8.4 Repetitiefactor

We zullen nu het begrip repetitiefactor bespreken. Stel men wil een record met vijf integers wegschrijven met format I3. Dit kan als volgt:
        WRITE (6, 600) I1, I2, I3, I4, I5
    600 FORMAT (1X, I3, I3, I3, I3, I3)
maar dit is omslachtig. Elk format kan voorafgegaan worden door een positief geheel getal, dat zegt hoe vaak het format herhaald moet worden. Dus het bovenstaande kan worden:
        WRITE (6, 600) I1, I2, I3, I4, I5
    600 FORMAT (1X, 5I3)
Let op het verschil in definitie van ``n'' en ``m'' in de format specificatie ``nIm'': ``n'' maal een integer van ``m'' cijfers. Een repetitiefactor mag ook een groep van formatspecificaties herhalen, bijvoorbeeld
        FORMAT (2(2X, I3, ',', F10.5)) = FORMAT (2X, I3, ',', F10.5, 2X,I3, ',', F10.5)
Merk op dat we hier de character konstante `` ',' '' hebben. Een groep formatspecifikaties is handig bij het schrijven van veel variabelen (een array bijvoorbeeld). Wanneer het meest rechtse haakje van het format bereikt wordt en nog niet alle variabelen zijn geschreven, wordt een nieuw output record (een nieuwe regel) begonnen en het format keert terug naar het openingshaakje behorend bij het één na laatste sluithaakje. Als voor dit openingshaakje een repetitiefactor staat doet dit opnieuw mee. Voorbeeld (let op formats met labels 510, 520 en 640):
        DOUBLE PRECISION A(3),B(2)
        CHARACTER*60 TITLE
   
     10 WRITE (6, 600)
    600 FORMAT (' ENTER TITLE OF RUN, (A60)')
        READ (5, 500, END=20) TITLE
    500 FORMAT (A60)

        WRITE (6, 610)
    610 FORMAT (' ENTER 3 A-WAARDEN, (F10.6)')
        READ (5, 510, END=20) A(1), A(2), A(3)
    510 FORMAT (3F10.6)

        WRITE (6, 620)
    620 FORMAT (' ENTER 2 B-WAARDEN, (E15.8)')
        READ (5, 520, END=20) B(1), B(2)
    520 FORMAT (2E15.8)

        WRITE (6, 630) TITLE
    630 FORMAT (1X, A60)
   
        WRITE (6, 640) A, B
    640 FORMAT (1X, 5E15.6)
        GO TO 10
   
     20 END

1.8.5 Implied DO list

We hebben het begrip implied DO list al even genoemd. Dit is een mogelijkheid die FORTRAN biedt om gedeeltes van arrays uit te schrijven of in te lezen. De syntax van de implied do list is:
        ( array(i), i=ondergrens, bovengrens )
Ook (stukken uit) twee- of meer-dimensionale arrays kunnen zo gelezen of geschreven worden. Dit gaat door te nesten. Bijvoorbeeld (2-dimensionaal):
        READ (5, 500)  ( (ARRAY(I,J), J=JMIN,JMAX), I=IMIN,IMAX )
De binnenste loop over J wordt herhaald voor alle in de buitenste loop gespecificeerde I waarden; in totaal worden (JMAX-JMIN+1)*(IMIN-IMAX+1) elementen van ARRAY ingelezen.

Voorbeeld:

        DIMENSION A(100), B(-10:10,-10:10)
   
  C     VUL A:
 
        DO 10 I=1,100
           A(I) = REAL(I)
     10 CONTINUE

  C     B WORDT HIER WAT MERKWAARDIG INGEVULD OM TE LATEN ZIEN DAT EEN
  C     EEN INCREMENT OOK NEGATIEF MAG ZIJN:
 
        DO 30 I=-10,10
           DO 20 J=10,-10,-1
              B(I,J) = REAL(I+J)
     20    CONTINUE
     30 CONTINUE

  C     DE GRENZEN VOOR DE IMPLIED DO LISTS:
        JMIN = -3
        JMAX = +3
        IMIN = -5
        IMAX =  0

  C     NU KOMEN TWEE IMPLIED DO LISTS, LET OP HET GEBRUIK VAN DE
  C     REPETITIEFACTOR VOOR EEN GROEP
 
        WRITE (6, 600) (A(I), I=31,41)
    600 FORMAT (5(2X, F10.6))
 
        WRITE (6, 610) ( (B(I,J), J=JMIN,JMAX), I=IMIN,IMAX)
    610 FORMAT (7 (1X, F9.5))
 
        END
Dit programma schrijft het volgende naar het scherm:
       31.000000   32.000000   33.000000   34.000000   35.000000
       36.000000   37.000000   38.000000   39.000000   40.000000
       41.000000
      -8.00000  -7.00000  -6.00000  -5.00000  -4.00000  -3.00000  -2.00000
      -7.00000  -6.00000  -5.00000  -4.00000  -3.00000  -2.00000  -1.00000
      -6.00000  -5.00000  -4.00000  -3.00000  -2.00000  -1.00000   0.00000
      -5.00000  -4.00000  -3.00000  -2.00000  -1.00000   0.00000   1.00000
      -4.00000  -3.00000  -2.00000  -1.00000   0.00000   1.00000   2.00000
      -3.00000  -2.00000  -1.00000   0.00000   1.00000   2.00000   3.00000
Verklaring:
De eerste elf getallen zijn een stuk van array A. Het format specificeert lengte F10.6 voor elk getal. Omdat de elementen slechts twee cijfers voor de komma hebben, wordt ieder element uit A links aangevuld met één spatie. Verder worden tussen de getallen twee spaties gespecificeerd met 2X. Dit wordt vijf maal gedaan, en dan is het het format aan zijn einde. Als dit gebeurt wordt een nieuw record begonnen (in dit geval een nieuwe regel) en begint het format weer aan de groep specificatie 5(2X,F10.6). Probeer zelf de uitvoer van B te begrijpen.

OPGAVEN:

  1. Maak een programma dat een binomiaal coëfficiënt in enkele precisie berekent. Deze is gedefinieerd door
        N \choose M} = N! \over  (N-M)!M!
    
    Vul eerst de hele array DLFAC, inclusief het element DLFAC(0)= 0., met dezelfde iteratie vergelijking als in de vorige opgave, maar geef nu DLFAC de grenzen 0:100. Lees dan $N$ en M in. Check of N .LT. M, of N .GT. 100 en of M .LT. 0. Als dit het geval is schrijf een foutenboodschap en ga weer naar READ (gebruik IF ... THEN ... ENDIF). Als de input in orde is bereken dan de logarithme van de binomiaal coëfficiënt, neem de exponent (function EXP) en schrijf het resultaat naar het scherm. Zet het programma in een oneindige lees loop. Schrijf de waarden
             6 \choose 0, 6 \choose 3, 6 \choose 6
    
    ter controle en schrijf dan
             100 \choose 5, 100 \choose 50    en   100 \choose 95
    
    over van het scherm op papier. Merk op dat theoretisch 100 \choose 5 = 100 \choose 95. Is dit zo?

  2. Maak het programma van de vorige opgave met dubbele precisie. Denk eraan dat de functie DBLE een integer converteert naar real. Bereken weer dezelfde binomiaal coëfficiënten en vergelijk met de waarden gevonden onder 8.

  3. Maak een programma dat ongeformatteerd op unit 1 n, m en n \choose m wegschrijft voor n >= m = 0, 1, ..., 10. Hier zijn n en m integer en de binomiaalcoëfficiënt is een dubbele precisie real. File FT01F00} bevat dus 66 FORTRAN records van ieder zestien bytes.

  4. Maak een programma dat de ongeformatteerde file uit de vorige opgave inleest (in een geneste DO met i=0, ..., 10 en j = 0, ..., i). Check of i klopt met de ingelezen waarde van n en j met m, geef een foutboodschap indien niet. Zet de binomiaalcoeëfficiënten in een array BINOM(0:10,0:10). Schrijf BINOM als de volgende onderdriehoeksmatrix naar het scherm. Iedere rij wordt voorafgegaan door het rijnummer. Let op het aantal spaties en cijfers achter de komma!
          123456789012345678901234567890123456789012345678901234567890123456789012345
     
            0      1.0000
            1      1.0000      1.0000
            2      1.0000      2.0000      1.0000
            3      1.0000      3.0000      3.0000      1.0000
            4      1.0000      4.0000      6.0000      4.0000      1.0000
            5      1.0000      5.0000     10.0000     10.0000      5.0000      1.0000
            6      1.0000      6.0000     15.0000     20.0000     15.0000      6.0000
                   1.0000
            7      1.0000      7.0000     21.0000     35.0000     35.0000     21.0000
                   7.0000      1.0000
            8      1.0000      8.0000     28.0000     56.0000     70.0000     56.0000
                  28.0000      8.0000      1.0000
            9      1.0000      9.0000     36.0000     84.0000    126.0000    126.0000
                  84.0000     36.0000      9.0000      1.0000
           10      1.0000     10.0000     45.0000    120.0000    210.0000    252.0000
                 210.0000    120.0000     45.0000     10.0000      1.0000
    


Updated 05-feb-1997, pfk