Fortran Programming Notes

Lin Jensen, Bishop's University
This page has been accessed [COUNTER] times since 19 March 1998

Topics

  1. Case construct
  2. File I/O
  3. Coping with older Fortran programs.
  4. Structures and Pointers, including example of a Linked List.
For more information, here is a Fortran site of interest, with products, compilers, software, and services listed, and here is the site where we got our complier, elf90, Lahey.

Syntax of the SELECT CASE statement

[construct-name:] SELECT CASE ( case-expr )
CASE (  case selector )  [construct identifier]
        ...
        ...   < statements >
        ...
CASE (  case selector )  [construct-name]
        ...
CASE (  case selector )  [construct-name]
        ...
CASE DEFAULT
        ...
END SELECT   [construct-name]
Where
Example:
SELECT CASE (Month)                       !!Find number of days in a Month
CASE (:0, 13:)
        Days_in_month = 0
        PRINT *, "Invalid month!!"
CASE (1, 3, 5, 7:8, 10, 12)
        Days_in_month = 31
CASE (2)                                !!February
        Days_in_month = 28
        IF (MOD(Year,4) == 0) Days_in_month = 29  !!Leap year
CASE DEFAULT                                    !!September, April, June & November
        Days_in_month = 30              !! Thirty days hath ...^
END SELECT

Fortran File input/output

Fortran associates files with unit numbers via the
OPEN statement. Once open, you can READ, WRITE, Position, and finally CLOSE the file, using its unit number.

A file is either "Formatted" or "Unformatted", and access to a file is either "Sequential" or "Direct". This gives rise to 4 types of files:

The OPEN statement

The OPEN statement opens a file for reading and/or writing by associating a unit number (unsigned integer) with the file name known to the system. It also specifies properties of the file using optional (keyword) parameters. A file remains open and accessable to all parts of a Fortran program (using the Unit number) until a
CLOSE statement is executed.

The following two OPEN statements are equivalent, since all of the optional keyword arguments in the second are the defaults.
OPEN (5, FILE="MYPROG.F90", POSITION="REWIND")
OPEN (UNIT=5, FILE="MYPROG.F90", FORM="FORMATTED", ACCESS="SEQUENTIAL", &
STATUS="UNKNOWN", POSITION="REWIND")

The arguments of OPEN are as follows:

READ and WRITE statements

READ transfers data from a file to variables (in its input-list ).
WRITE does the reverse, transfering values (which can be expressions) from its output-list to the file. Both statements use an identical set of io-control-specs , which we illustrate with a write statement. More examples will be found under the different types of files, this one is Formatted Direct, to show all the options. The first 2 are often positional, without the keyword.

WRITE (UNIT=7, FMT=78, REC=MyRec, ERR=20, END=30, ADVANCE="YES") output-list

CLOSE statement

CLOSE(5) closes previously opened file 5. It is kept by default, unless the file was opened as STATUS="SCRATCH", which cannot be kept.

Formatted sequential files

This is a text file, consisting of characters. It is portable between computer systems and software. Data is converted between characters in the file and binary representation in memory, either by explicit format, or Fortran's choice list-directed formatting, using * as the format specifier. This works exactly the same way as reading from standard input and writing to standard output. In fact, these special files are always open, and specified by UNIT=*.

In this example, we will write a file named MY.TXT, which will overwrite any previous version.

    INTEGER :: Number, Count, Quantity
    REAL    :: Amount, Total
    CHARACTER(20) :: Description
 
    OPEN (5, FILE="MY.TXT", STATUS="REPLACE", POSITION="REWIND")
    WRITE (5,*) "This is the file written by me"    !!list directed
    WRITE (5,*)                                     !!blank line
    WRITE (5,500) "Quantity","Description","Price","Sub-Total"
                                                    !!column headings
    WRITE (5,500) "========","===========","=====","========="
                                                    !!underline them
    ......
    !! Variables get some values, we accumulate a Total
    ......
    WRITE (5, 501) Quantity, Description, Amount, Amount*Quantity
    ......
    WRITE (UNIT=5, FMT=502) Total
    CLOSE (5)
    ......
!! --------   Formats -------------
500 FORMAT (A10, A20, 2A12)
501 FORMAT (I10, A20, 2F12.2)   !!dollar amounts with 2 places after .
502 FORMAT ("Total cost for this list of items is",T42, F12.2)

Unformatted sequential files

This is a binary file, data is stored exactly as in memory. Thus, it is shorter than an equivalent formatted file, and access is faster. However, such a file can only be read by a Fortran program on the same (type of) system as it was written. An important aspect of Unformatted files is that no accuracy is lost when storing REAL data.

A record is the data transfered by a single READ or WRITE statement. Lengths of records can vary. It is important that the type, kind, and shape of variables in a READ statement match those of the WRITE statement that produced the same record.

In this example, a series of records each store a person's name, and a list of their numbered Swiss bank accounts and balances. Except that the first record stores the total number of records. Note that it is necessary to store the length of a variable-length record before the variable data, so that it can be read in time to control the implied DO loop.

PROGRAM Top_Secret
!! Example of Unformatted Sequential file
    IMPLICIT NONE
    INTEGER :: N, Account(20), A
    REAL    :: Balance(20)          !!Holdings expressed in Swiss Francs
    CHARACTER(30) :: Name
 
    OPEN (6, FILE="swiss.bin", STATUS="NEW", FORM="UNFORMATTED", &
            POSITION="REWIND", ACTION="WRITE")
    DO
        CALL Enter_a_Person (Name, N, Account, Balance)
        IF (Name ==" ") EXIT
        WRITE (6) Name, N, (Account(A), Balance(A), A=1,N)
    END DO
    CLOSE (6, STATUS="KEEP")
!! ----------- now read it again -----
    OPEN (6, FILE="swiss.bin", STATUS="OLD", FORM="UNFORMATTED", &
            POSITION="REWIND", ACTION="READ")
    PRINT *, "Holders of Swiss bank accounts, and their holdings"
    DO
        READ (6, END=30) Name, N, (Account(A), Balance(A), A=1,N)
!!     - - - - - Note: All values must be read, even if we don't use Account
        PRINT "(' ',A, F12.2)", Name, SUM (Balance(:N))
    END DO
30  CLOSE (6)
    STOP
END PROGRAM Top_secret

Formatted direct files

Such files have fixed-length lines of text as records. They can be printed, and also individual lines can be accessed directly by line number (
REC=). An explicit format specifier (not * ) must be present in READ or WRITE statements.

In this example, a Medical lab that does blood tests registers patients with a patient number starting with 2. They want to be able to print the current patient list, and also access patient name, etc directly by number. Note that we are using the

We first set up a file with only the header record:

PROGRAM Start_Patient
    IMPLICIT NONE
    CHARACTER(8) :: Date
    INTEGER :: Next_Patient = 2     !! Next number to assign
                    !! Patient numbers 2:NextPatient-1 exist
    CALL DATE_AND_TIME (Date)       !! 8 character system date
    OPEN (UNIT=7, FILE="PATIENT.TXT", STATUS="NEW", ACCESS="DIRECT", &
        FORM="FORMATTED", ACTION="WRITE", RECL=82)
    WRITE (7, 77, REC=1) "Patient registrations, next patient is", &
        Next_Patient, "Last updated on ", Date
77  FORMAT (A, T40, I6, A20, A8)
    CLOSE(7)
    STOP
END PROGRAM Start_Patient

Next we present a subroutine to access this file to add a new patient. Of course, it might also be a good idea to check that the health insurance number does not already occur in the file. We will skip around, reading record 1, writing a new record, presumably at the end, and finally writing an updated record 1. The intervening records do not have to be processed. Note that the same RECL=82 must be specified.

SUBROUTINE New_Patient (Name, Phone, RAMQ_ID, Dr_ID, Remark, Next_Patient)
    IMPLICIT NONE
!! -- Fields of patient records
    CHARACTER(25), INTENT(IN) :: Name           !!Patient name
    CHARACTER(12), INTENT(IN) :: Phone          !!Patient's phone number
    CHARACTER(4),  INTENT(IN) :: RAMQ_ID (3)    !!Health insurance no
                  !!LLLF YYMM DDSS This encodes date of birth and sex
    INTEGER      , INTENT(IN) :: Dr_ID          !!Doctor code
    CHARACTER(20), INTENT(IN) :: Remark         !!other remarks
 
    INTEGER, INTENT(OUT) :: Next_Patient   !! Next number to assign
                    !! Patient numbers 2:NextPatient-1 exist
    CHARACTER(8) :: Date, Check
 
77  FORMAT (A, T40, I6, A20, A8)           !!for 1st line, <80 char.
78  FORMAT (A25, A12, TR2, 3A5, I4, A21)   !!all other lines, 80 ch.
 
    CALL DATE_AND_TIME (Date)       !! 8 character system date
    OPEN (UNIT=7, FILE="PATIENT.TXT", STATUS="OLD", ACCESS="DIRECT", &
        FORM="FORMATTED", ACTION="READWRITE", RECL=82)
    READ (7, 77, REC=1) Check, Next_Patient
    WRITE (7, 78, REC=Next_Patient) Name, Phone, RAMQ_ID, Dr_ID, Remark
    WRITE (7, 77, REC=1) "Patient registrations, next patient is", &
        Next_Patient + 1, "Last updated on ", Date
    CLOSE(7)
    RETURN
END SUBROUTINE New_Patient

Finally, this function returns Patient Name, based on patient number. It could check to see that the number is valid, by also reading record 1. We have chosen to use the ERR= option to return an error name instead, should there be any problem with I/O. In this OPEN statement, the default FORM="FORMATTED" is assumed. The FORMAT statements used for READ or WRITE must appear in this subprogram unit as well as any others that read/write the file. They need not be identical, as long as they interpret the same columns of data consistantly. In this example, we only need to read Patient_Name, because it is the first data item interpreted by the Format.

FUNCTION Patient_Name (Patient_Number)
    IMPLICIT NONE
    CHARACTER(25) :: Patient_Name
    INTEGER, INTENT(IN) :: Patient_Number
 
    OPEN (7, FILE="PATIENT.TXT", ACCESS="DIRECT", STATUS="OLD", &
             ACTION="READ", RECL=82)
    READ (7, 78, REC=Patient_Number, ERR=33) Patient_name
78  FORMAT (A25, A12, TR2, 3A5, I4, A21)   !!all other lines, 80 ch.
    RETURN
!! ----- OR, in case of error in reading
33  Patient_name = "Patient number not found"
    RETURN
END FUNCTION Patient_Name
 
When this function is used, it looks very much like an array reference.

Unformatted Direct Files

Such files are characterized by fixed size records and fast, direct access by record number. There is no conversion between internal and external representation of data, so in particular, no accuracy is lost when storing REAL data. No Format is required (or allowed, even) for
READ or WRITE statements. For example,
    READ (8,REC=42) Name, Age, Height, Weight 
When opening the file, record length must be specified with RECL =. This can be calculated from the number, kind, and length of data items read/written (by the longest statement). A direct file that is STATUS="OLD" must be opened with the same RECL as when it was created.

For example, the same medical lab of our formatted-direct example receives blood samples, which are numbered sequentially in the "Incomming Blood Book". We wish to determine this number, and then record the Patient_Number, the date, the amount of blood (in cc's, a real number), and up to 6 standard tests (5 tiny integers). Later, when the test results come in, we will consult this file and the patient file (and a Doctor file) to prepare a report for the Doctor.

First, here is the subroutine to record incoming blood. (The file needs to be created with Next_Blood in REC=1, similar to the previous example.) By adding up the space required for the WRITE statement, we find that 16 bytes are needed for each record (fewer for record 1). Actually it is desirable for record length to be a power of 2.

SUBROUTINE Incoming_Blood (Patient_Number, Volume, Tests, Blood_Number)
    IMPLICIT NONE
    INTEGER(kind=2), INTENT(IN) :: Patient_Number
    REAL(kind=4),    INTENT(IN) :: Volume           !!cc's of blood rec'd
    INTEGER(kind=1), INTENT(IN) :: Tests(6)
    INTEGER,         INTENT(OUT):: Blood_Number
!! --- local variables
    INTEGER :: DateTime(8)          !!see DATE_TIME intrinsic function
    INTEGER(kind=2) :: Year
    INTEGER(kind=1) :: Month, Day
 
!! --- get date from system
    CALL DATE_TIME (values=DateTime)
    Year = DateTime(1)              !!example, 1996
    Month= DateTime(2)
    Day  = DateTime(3)
!! --- open file and read next blood number
    OPEN (8, FILE="blood", ACCESS="DIRECT", FORM="UNFORMATTED",&
             STATUS="OLD", ACTION="READWRITE", RECL=16)
    READ  (8, REC=1) Blood_Number
    WRITE (8, REC=Blood_Number) Patient_Number, Year, Month, Day, &
            Volume, Tests
    WRITE (8, REC=1) Blood_Number + 1, Year, Month, Day
    RETURN
END SUBROUTINE Incoming_Blood
Now when test results come back, we start with blood number and look up patient number, etc., which can be used for writing a report. We can use the previously given Function Patient_Name to print the actual name.
SUBROUTINE Get_Blood
END SUBROUTINE Get_Blood

Internal "Files"

An "internal file" is simply a character variable. It is used in a READ or WRITE statement in place of a unit and conversion is performed between character text and the i/o list just as is done with standard input and output.

Example, convert an integer to characters:

    CHARACTER(8) :: CHARINT
    INTEGER       :: Example = -12345
 
    WRITE (CHARINT, "(I8)") Example
    WRITE *, "The number is" // ADJUSTL(CharInt)//"<---"
will write
The number is-12345  <---
with 2 spaces after the 5 due to the action of ADJUSTL
Last updated 18 March 1996

Up to top of this document
Back to Lin Jensen's home page.
Up to Bishop's University www.cs.ubishops.ca