next up previous contents
Next: Fortran in Practice Up: Professional Programmer's Guide to Previous: What Is Fortran?

Basic Fortran Concepts

This section presents some of the basic ideas of Fortran by showing some complete examples. In the interests of simplicity, the problems which these solve are hardly beyond the range of a good pocket calculator, and the programs shown here do not include various refinements that would usually be present in professional software. They are, however, complete working programs which you can try out for yourself if you have access to a Fortran system. If not, it is still worth reading through them to see how the basic elements of Fortran can be put together into complete programs.

Statements

To start with, here is one of the simplest program that can be devised:

 
       PROGRAM TINY 
       WRITE(UNIT=*, FMT=*) 'Hello, world' 
       END
As you can probably guess, all this program does is to send a rather trite message ``Hello, world" to your terminal. Even so its layout and structure deserve some explanation.

The program consists of three lines, each containing one statement. Each Fortran statement must have a line to itself (or more than one line if necessary), but the first six character positions on each line are reserved for statement labels and continuation markers. Since the statements in this example need neither of these features, the first six columns of each line have been left blank.

The PROGRAM statement gives a name to the program unit and declares that it is a main program unit. Other types of program unit will be covered later on. The program can be called anything you like provided the name conforms to the Fortran rules; the first character of a Fortran symbolic name must be a letter but, unfortunately, they cannot be more than six characters long in total. It is generally sensible to give the same name to the program and to the file which holds the Fortran source code (the original text).

The WRITE statement produces output: the parentheses enclose a list of control items which determine where and in what form the output appears. UNIT=* selects the standard output file which is normally your own terminal; FMT=* selects a default output layout (technically known as list-directed format). Asterisks are used here, as in many places in Fortran, to select a default or standard option. This program could, in fact, have been made slightly shorter by using an abbreviated form of the WRITE statements:

 
      WRITE(*,*) 'Hello, world'
Although the keywords UNIT= and FMT= are optional, they help to make the program more readable. The items in the control list, like those in all lists in Fortran, are separated by commas.

The control information in the WRITE statement is followed by a list of the data items to be output: here there is just one item, a character constant which is enclosed in a pair of apostrophe (single quote) characters.

An END statement is required at the end of every program unit. When the program is compiled (translated into machine code) it tells the compiler that the program unit is complete; when encountered at run-time the END statement stops the program running and returns control to the operating system.

The Standard Fortran character set does not contain any lower-case letters so statements generally have to be written all in upper case. But Fortran programs can process as data any characters supported by the machine; character constants (such as the message in the last example) are not subject to this constraint.

Expressions and Assignments

The next example solves a somewhat more realistic problem: it computes the repayments on a fixed-term loan (such as a home mortgage loan). The fixed payments cover the interest and repay part of the capital sum; the annual payment can be calculated by the following formula:


displaymath2875

In this formula, rate is the annual interest rate expressed as a fraction; since it is more conventional to quote interest rates as a percentage the program does this conversion for us.

 
       PROGRAM LOAN 
       WRITE(UNIT=*, FMT=*)'Enter amount, % rate, years' 
       READ(UNIT=*, FMT=*) AMOUNT, PCRATE, NYEARS 
       RATE = PCRATE / 100.0 
       REPAY = RATE * AMOUNT / (1.0 - (1.0+RATE)**(-NYEARS)) 
       WRITE(UNIT=*, FMT=*)'Annual repayments are ', REPAY 
       END
This example introduces two new forms of statement: the READ and assignment statements, both of which can be used to assign new values to variables.

The READ statement has a similar form to WRITE: here it reads in three numbers entered on the terminal in response to the prompt and assigns their values to the three named variables. FMT=* again selects list-directed (or free-format) input which allows the numbers to be given in any convenient form: they can be separated by spaces or commas or even given one on each line.

The fourth statement is an assignment statement which divides PCRATE by 100 and assigns the result to another variable called RATE. The next assignment statement evaluates the loan repayment formula and assigns the result to a variable called REPAY.

Several arithmetic operators are used in these expressions: as in most programming languages ``/'' represents division and ``*'' represents multiplication; in Fortran ``**'' is used for exponentiation, i.e. raising one number to the power of another. Note that two operators cannot appear in succession as this could be ambiguous, so that instead of ``**-N'' the form ``**(-N)'' has to be used.

Another general point concerning program layout: spaces (blanks) are not significant in Fortran statements so they can be inserted freely to improve the legibility of the program.

When the program is run, the terminal dialogue will look something like this:

 
Enter amount, % rate, years 
20000, 9.5, 15 
Annual repayments are    2554.873
The answer given by your system may not be exactly the same as this because the number of digits provided by list-directed formatting depends on the accuracy of the arithmetic, which varies from one computer to another.

Integer and Real Data Types

The LOAN program would have been more complicated if it had not taken advantage of some implicit rules of Fortran concerning data types: this requires a little more explanation.

Computers can store numbers in several different ways: the most common numerical data types are those called integer and real. Integer variables store numbers exactly and are mainly used to count discrete objects. Real variables are useful many other circumstances as they store numbers using a floating-point representation which can handle numbers with a fractional part as well as whole numbers. The disadvantage of the real data type is that floating-point numbers are not stored exactly: typically only the first six or seven decimal digits will be correct. It is important to select the correct type for every data item in the program. In the last example, the number of years was an integer, but all of the other variables were of real type.

The data type of a constant is always evident from its form: character constants, for example, are enclosed in a pair of apostrophes. In numerical constants the presence of a decimal point indicates that they are real and not integer constants: this is why the value one was represented as ``1.0" and not just ``1".

There are several ways to specify the data type of a variable. One is to use explicit type statements at the beginning of the program. For example, the previous program could have begun like this:

 
       PROGRAM LOAN 
       INTEGER NYEARS 
       REAL AMOUNT, PCRATE, RATE, REPAY

Although many programming languages require declarations of this sort for every symbolic name used in the program, Fortran does not. Depending on your point of view, this makes Fortran programs easier to write, or allows Fortran programmers to become lazy. The reason that these declarations can often be omitted in Fortran is that, in the absence of an explicit declaration, the data type of any item is determined by the first letter of its name. The general rule is:


tabular70

In the preceding program, because the period of the loan was called NYEARS (and not simply YEARS) it automatically became an integer, while all the other variables were of real type.

DO Loops

Although the annual repayments on a home loan are usually fixed, the outstanding balance does not decline linearly with time. The next program demonstrates this with the aid of a DO-loop.

 
      PROGRAM REDUCE 
      WRITE(UNIT=*, FMT=*)'Enter amount, % rate, years' 
      READ(UNIT=*, FMT=*) AMOUNT, PCRATE, NYEARS 
      RATE = PCRATE / 100.0 
      REPAY = RATE * AMOUNT / (1.0 - (1.0+RATE)**(-NYEARS)) 
      WRITE(UNIT=*, FMT=*)'Annual repayments are ', REPAY 
      WRITE(UNIT=*, FMT=*)'End of Year  Balance' 
      DO 15,IYEAR = 1,NYEARS 
          AMOUNT = AMOUNT + (AMOUNT * RATE) - REPAY 
          WRITE(UNIT=*, FMT=*) IYEAR, AMOUNT  
15    CONTINUE 
      END
The first part of the program is similar to the earlier one. It continues with another WRITE statement which produces headings for the two columns of output which will be produced later on.

The DO statement then defines the start of a loop: the statements in the loop are executed repeatedly with the loop-control variable IYEAR taking successive values from 1 to NYEARS. The first statement in the loop updates the value of AMOUNT by adding the annual interest to it and subtracting the actual repayment. This results in AMOUNT storing the amount of the loan still owing at the end of the year. The next statement outputs the year number and the latest value of AMOUNT. After this there is a CONTINUE statement which actually does nothing but act as a place-marker. The loop ends at the CONTINUE statement because it is attached to the label, 15, that was specified in the DO statement at the start of the loop.

The active statements in the loop have been indented a little to the right of those outside it: this is not required but is very common practice among Fortran programmers because it makes the structure of the program more conspicuous.

The program REDUCE produces a table of values which, while mathematically correct, is not very easy to read:

 
Enter amount, % rate, years 
2000, 9.5, 5 
Annual repayments are    520.8728     
End of Year  Balance 
      1   1669.127     
      2   1306.822     
      3   910.0968     
      4   475.6832     
      5  2.9800416E-04

Formatted Output

The table of values would have a better appearance if the decimal points were properly aligned and if there were only two digits after them. The last figure in the table is actually less than a thirtieth of a penny, which is effectively zero to within the accuracy of the machine. A better layout can be produced easily enough by using an explicit format specification instead of the list-directed output used up to now. To do this, the last WRITE statement in the program should be replaced with one like this:
WRITE(UNIT=*, FMT='(1X,I9,F11.2)') IYEAR, AMOUNT
The amended program will then produce a neater tabulation:

 
Enter amount, % rate, years 
2000, 9.5, 5 
Annual repayments are    520.8728     
End of Year  Balance 
        1    1669.13 
        2    1306.82 
        3     910.10 
        4     475.68 
        5        .00
The format specification has to be enclosed in parentheses and, as it is actually a character constant, in a pair of apostrophes as well. The first item in the format list, 1X, is needed to cope with the carriage-control convention: it provides an additional blank at the start of each line which is later removed by the Fortran system. There is no logical explanation for this: it is there for compatibility with very early Fortran system. The remaining items specify the layout of each number: I10 specifies that the first number, an integer, should be occupy a field 10 columns wide; similarly F11.2 puts the second number, a real (floating-point) value, into a field 11 characters wide with exactly 2 digits after the decimal point. Numbers are always right-justified in each field. The field widths in this example have been chosen so that the columns of figures line up satisfactorily with the headings.

Functions

Fortran provides a useful selection of intrinsic functions to carry out various mathematical operations such as square root, maximum and minimum, sine, cosine, etc., as well as various data type conversions. You can also write your own functions. The next example, which computes the area of a triangle, shows both forms of function in action.

The formulae for the area of a triangle with sides of length a, b, and c is:
displaymath2877

displaymath2878

 
      PROGRAM TRIANG 
      WRITE(UNIT=*,FMT=*)'Enter lengths of three sides:' 
      READ(UNIT=*,FMT=*) SIDEA, SIDEB, SIDEC 
      WRITE(UNIT=*,FMT=*)'Area is ', AREA3(SIDEA,SIDEB,SIDEC) 
      END 

      FUNCTION AREA3(A, B, C) 
*Computes the area of a triangle from lengths of sides 
      S = (A + B + C)/2.0 
      AREA3 = SQRT(S * (S-A) * (S-B) * (S-C)) 
      END
This program consists of two program units. The first is the main program, and it has as similar form to those seen earlier. The only novel feature is that the list of items output by the WRITE statement includes a call to a function called AREA3. This computes the area of the triangle. It is an external function which is specified by means of a separate program unit technically known as a function subprogram.

The external function starts with a FUNCTION statement which names the function and specifies its set of dummy arguments. This function has three dummy arguments called A, B, and C. The values of the actual arguments, SIDEA, SIDEB, and SIDEC, are transferred to the corresponding dummy arguments when the function is called. Variable names used in the external function have no connection with those of the main program: the actual and dummy argument values are connected only by their relative position in each list. Thus SIDEA transfers its value to A, and so on. The name of the function can be used as a variable within the subprogram unit; this variable must be assigned a value before the function returns control, as this is the value returned to the calling program.

Within the function the dummy arguments can also be used as variables. The first assignment statement computes the sum, divides it by two, and assigns it to a local variable, S; the second assignment statement uses the intrinsic function SQRT which computes the square-root of its argument. The result is returned to the calling program by assigning it to the variable which has the same name as the function.

The END statement in a procedure does not cause the program to stop but just returns control to the calling program unit.

There is one other novelty: a comment line describing the action of the function. Any line of text can be inserted as a comment anywhere except after an END statement. Comment lines have an asterisk in the first column.

These two program units could be held on separate source files and even compiled separately. An additional stage, usually called linking, is needed to construct the complete executable program out of these separately compiled object modules. This seems an unnecessary overhead for such simple programs but, as described in the next section, it has advantages when building large programs.

In this very simple example it was not really necessary to separate the calculation from the input/output operations but in more complicated cases this is usually a sensible practice. For one thing it allows the same calculation to be executed anywhere else that it is required. For another, it reduces the complexity of the program by dividing the work up into small independent units which are easier to manage.

IF-blocks

Another important control structure in Fortran is the IF statement which allows a block of statements to be executed conditionally, or allows a choice to be made between different courses of action.

One obvious defect of the function AREA3 is that has no protection against incorrect input. Many sets of three real numbers could not possibly form the sides of a triangle, for example 1.0, 2.0, and 7.0. A little analysis shows that in all such impossible cases the argument of the square root function will be negative, which is illegal. Fortran systems should detect errors like this at run-time but will vary in their response. Even so, a message like "negative argument for square-root" may not be enough to suggest to the user what is wrong. The next version of the function is slightly more user-friendly:

 
      REAL FUNCTION AREA3(A, B, C) 
*Computes the area of a triangle from lengths of its sides. 
*If arguments are invalid issues error message and returns zero. 
      REAL A, B, C 
      S = (A + B + C)/2.0 
      FACTOR = S * (S-A) * (S-B) * (S-C) 
      IF(FACTOR .LE. 0.0) THEN 
          WRITE(UNIT=*, FMT=*)'Impossible triangle', A, B, C 
          AREA3 = 0.0 
      ELSE 
          AREA3 = SQRT(FACTOR) 
      END IF 
      END
The IF statement works with the ELSE and END IF statements to enclose two blocks of code. The statements in the first block are only executed if the expression in the IF statement is true, those in the second block only if it is false. The statements in each block are indented for visibility, but this is, again, just a sensible programming practice.

With this modification, the value of FACTOR is tested and if it is negative or zero then an error message is produced; AREA3 is also set to an impossible value (zero) to flag the mistake. Note that the form ``.LE.'' is used because the less-than-or-equals character, ``<", is not present in the Fortran character set. If S is positive the calculation proceeds as before.

Arrays

Fortran has good facilities for handling arrays. They can have up to seven dimensions. The program STATS reads a set of real numbers from a data file and puts them into a one-dimensional array. It then computes their mean and standard deviation. Given an array of values tex2html_wrap_inline2885, the mean tex2html_wrap_inline2887 standard deviation tex2html_wrap_inline2889 are given by:


displaymath2881


displaymath2882

To simplify this program, it will be assumed that the first number in the file is an integer which tells the program how many real data points follow.

 
       PROGRAM STATS 
       CHARACTER FNAME*50 
       REAL X(1000) 
       WRITE(UNIT=*, FMT=*) 'Enter data file name:' 
       READ(UNIT=*, FMT='(A)') FNAME 
       OPEN(UNIT=1, FILE=FNAME, STATUS='OLD') 
*Read number of data points NPTS 
       READ(UNIT=1, FMT=*) NPTS 
       WRITE(UNIT=*, FMT=*) NPTS, ' data points' 
       IF(NPTS .GT. 1000) STOP 'Too many data points' 
       READ(UNIT=1, FMT=*) (X(I), I = 1,NPTS) 
       CALL MEANSD(X, NPTS, AVG, SD) 
       WRITE(UNIT=*, FMT=*) 'Mean =', AVG, ' Std Deviation =', SD 
       END 

       SUBROUTINE MEANSD(X, NPTS, AVG, SD) 
       INTEGER NPTS 
       REAL X(NPTS), AVG, SD 
       SUM   = 0.0 
       SUMSQ = 0.0 
       DO 15, I = 1,NPTS 
            SUM   = SUM   + X(I) 
            SUMSQ = SUMSQ + X(I)**2 
15       CONTINUE 
       AVG = SUM / NPTS 
       SD  = SQRT(SUMSQ - NPTS * AVG)/(NPTS-1) 
       END
This program has several new statement forms.

The CHARACTER statement declares that the variable FNAME is to hold a string of 50 characters: this should be long enough for the file-names used by most operating systems.

The REAL statement declares an array X with 1000 elements numbered from X(1) to X(1000).

The READ statement uses a format item A which is needed to read in a character string: A originally stood for ``alpha-numeric".

The OPEN statement then assigns I/O unit number one (any small integer could have been used) to the file. This unit number is needed in subsequent input/output statements. The item STATUS='OLD' is used to specify that the file already exists.

The IF statement is a special form which can replace an IF-block where it would only contain one statement: its effect is to stop the program running if the array would not be large enough.

The READ statement which follows it has a special form known as an implied-DO-loop: this reads all the numbers from the file in to successive elements of the array X in one operation.

The CALL statement corresponds to the SUBROUTINE statement in the same way that a function reference corresponded to a FUNCTION statement. The difference is that the arguments X and NPTS transfer information into the subroutine, whereas AVG and SD return information from it. The direction of transfer is determined only by the way the dummy arguments are used within the subroutine. An argument can be used to pass information in either direction, or both.

The INTEGER statement is, as before, not really essential but it is good practice to indicate clearly the data type of every procedure argument.

The REAL statement declares that X is an array but uses a special option available only to dummy arguments: it uses another argument, NPTS, to specify its size and makes it an adjustable array. Normally in Fortran array bounds must be specified by constants, but the rules are relaxed for arrays passed into procedures because the actual storage space is already allocated in the calling program unit; the REAL statement here merely specifies how many of the 1000 elements already allocated are actually to be used within the subroutine.

The rest of the subroutine uses a loop to accumulate the sum of the elements in SUM, and the sum of their squares in SUMSQ. It then computes the mean and standard deviation using the usual formulae, and returns these values to the main program, where they are printed out.


next up previous contents
Next: Fortran in Practice Up: Professional Programmer's Guide to Previous: What Is Fortran?

Clive Page
Tue Feb 27 11:14:41 GMT 2001