Control Structures

Program statements are usually executed in a linear fashion. `Control structures' is the term we use for language constructs that change this linear execution.

Jumps

The simplest way to interrupt the linear flow of a program is to transfer control to another statement, using the GOTO statement:

      GOTO label

This is called the `unconditional GOTO' statement.

Corrresponding to this jump instruction, there has to be a statement in the same main or sub program that carries this label:

label [statement]

Note that label is a number of five digits at maximum because of the meaning of Fortran's columns.

In the jump statement the label has to be written explicitly; you can not use a variable that has a value that is an existing label.

Jumps are rarely needed. In Fortran66 they were essential for coding conditionals, but in Fortran77 you can largely do without them. Their most common application is for premature termination of a DO loop.

Assigned GOTO

It is possible to assign a statement label to an integer variable and to jump to the corresponding label. The assignment has to be done with an ASSIGN statement:

      ASSIGN 25 TO ilabel

After this one can write

      GOTO ilabel

The assigned GOTO is not considered good programming practice. You really don't need it.

Conditionals

An algorithm often requires a choice between two different actions, depending on some condition. The Fortran language construct for this is the conditional or IF statement. There are three different conditionals.

Logical IF

The `logical IF statement' is the simplest conditional: it states "if some condition holds, execute one single statement".

      IF (condition) statement

The condition is any logical variable or logical expression.

Block IF

The above logical IF statement is rather limited. To choose between two blocks of instructions one can use the block IF statement:

      IF (condition) THEN
         statements
      ELSE
         statements
      ENDIF

The ELSE part is optional, but the list of statements in the THEN part can be empty.

If a number of conditions have to be checked, one can write nested conditionals:

      IF (condition1) THEN
         IF (condition2) THEN
            statements
         ELSE
            statements
         ENDIF
      ENDIF

or

      IF (condition1) THEN
         statements
      ELSE
         IF (condition2) THEN
            statements
         ELSE
            statements
         ENDIF
      ENDIF

and the last nesting can be abbreviated as:

      IF (condition1) THEN
         statements
      ELSE IF (condition2) THEN
         statements
      ELSE
         statements
      ENDIF

The block IF did not exist in Fortran66; instead it had to be emulated with a logical IF and jumps as

      IF (condition) GOTO 10
         statements
         GOTO 20
10    CONTINUE
         statements
20    CONTINUE

with the THEN and ELSE part statements in the wrong order, or

      IF (condition) GOTO 10
      GOTO 20
10    CONTINUE
         statements
         GOTO 30
20    CONTINUE
         statements
30    CONTINUE

in a more logical order but with more jumps.

Example of the use of Block IF

Consider the problem of finding the roots of a second degree polynomial ax^2+bx+c. The roots are given by

      x1 = (-b+sqrt(b**2-4*a*c))/(2*a)
      x2 = (-b-sqrt(b**2-4*a*c))/(2*a)

subject to the following conditions:

Thus we get the following algorithm:

      REAL a,b,c,disc,x1,x2

      IF (a.EQ.0.0) THEN
         PRINT *,'This is not a 2nd degree polynomial'
      ELSE
         disc = b**2-4*a*c
         IF (disc.LT.0.0) THEN
            PRINT *,'There are no real roots'
         ELSE IF (disc.EQ.0.0) THEN
            x1 = -b/(2*a)
            x2 = x1
            PRINT *,'The only root is',x1
         ELSE
            disc = sqrt(disc)
            x1 = (-b+disc)/(2*a)
            x2 = (-b-disc)/(2*a)
         END IF
      END IF

From a numerical point of view there are still several remarks to be made about this algorithm. First of all, there are two tests on zero; at least the second one should be replaced. Then, the computation of one of the roots will suffer from loss of precision, and should be done in a different manner. We will not discuss that here.

Arithmetic IF

For the common occurrence of a three-way choice depending on whether a number is negative, zero, or positive, there exists the `arithmetic IF' statement:

      IF (value) label1,label2,label3

This construct can be easily emulated with a block IF statement, and it is in fact considered bad programming practice.

DO Loop

Repeated execution of a group of statements is effected in Fortran by the DO loop construct:

      DO label var=low,high,step
         statements
label final statement

where var is an integer variable, the loop variable, and low and high are integer values, the bounds. The integer step value is optional; if omitted it is assumed to be one.

Number of iterations

The block of statements and the final statement are traversed a number of times, with var set successively to the values from low to high. This results in a number of iterations equal to (high-low)/step+1, where the division is rounded down.

You may be tempted to tinker with this number of iterations by altering the bounds or the loop variable during the iteration process, but that is against the language standard, and the results of this are entirely compiler-dependent. You can only legitimately cut a loop short by jumping out of it.

Nested loops

A loop body can contain another loop:

      DO 10 i=1,m
         DO 20 j=1,n
            ...
20       CONTINUE
10    CONTINUE

The nesting needs to be proper: the 20 and 10 statements can not be reversed. It is allowed to terminate both loops with the same labeled statement, but that is not considered good programming practice.

The final statement

It is considered good programming practice to let the final statement be the CONTINUE statement:

      DO 10 i=2,15
         statements
10    CONTINUE

but any statement that is not patently nonsensical (eg a GOTO) is allowed.

Fortran90 extension: Many compilers have an extension to allow unlabeled loop termination, and this is an official part of Fortran90:

      DO i=2,15
         statements
      END DO

Bounds and step size

If a step value is specified, it is possible that the high value is never actually attained. In that case the loop terminates if the next iteration would have var be greater than high (less than, if a step size less than zero is used). Example: in

      DO 10 i=1,6,2
         ...
10    CONTINUE

the loop is executed three times, with the values 1,3,5 for the loop variable.

If the upper bound is less than the lower bound, the loop body is not executed. (In Fortran66 it was executed once; this necessitated explicit tests before the loop to see whether the upper bound was at least as great as the lower bound.)

The bounds, iteration variable, and step size can be REAL instead of integer variables, but this is bad programming practice because real arithmetic is not exact. The same code could terminate earlier or later on one machine than on another. Instead of

      DO 10 x=.25,1.,.25

write

      DO 10 i=1,4
         x = i*.25

Examples of DO loops

Suppose you want a list of the squares of all natural numbers under one hundred. The following DO loop accomplishes this:

      DO 10 i=1,100
         PRINT *,i,i**2
10    CONTINUE

If you wanted only the squares of the even numbers, you need to set a step size:

      DO 10 i=2,100,2
         PRINT *,i,i**2
10    CONTINUE

For the squares of the odd numbers this loop does the job:

      DO 10 i=1,100,2
         PRINT *,i,i**2
10    CONTINUE

In this case the loop variable does not actually take the value of the upper bound: after the value 99, which is less than the upper bound, the next value, 101, would be larger than the upper bound, so the loop terminates after the value 99 has been processed.

Premature termination of a loop

Sometimes it is necessary to terminate a loop before the full number of iterations has been performed. Alternatively, maybe the purpose of the loop was to iterate until a certain condition is met, and the number of iterations has been set artificially high. (For this latter purpose one would use a WHILE loop in Fortran90 or when it is made available by the compiler as a language extension.)

Premature exit from a loop is one of the few places where a GOTO instruction is the best choice:

      DO 10 i=1,10000000
         ...
         IF (condition) GOTO 20
         ...
10    CONTINUE
20    CONTINUE

Be sure not to jump to the 10 label terminating the loop, as that will most likely skip to the next iteration, not to the statements after the loop.

Often, a loop serves to find the index for which a condition holds. For this, the index holds its last value after the loop:

      DO 10 i=1,1000
         ...
         IF (condition) GOTO 20
         ...
10    CONTINUE
20    found = i

Fortran90 extension: exiting a loop is done with the EXIT instruction, and skipping to the next iteration is done with CYCLE.

Examples of a jump out of a DO loop

Suppose you want to find the largest number such that its square is less than one thousand. A program for finding that number could be based on a DO loop with a jump out of it when the number has been found:

      DO 10 num=1,1000
         ...
         IF ( some condition ) GOTO 20
         ...
10    CONTINUE
20    ...

Here is an attempt:

      DO 10 num=1,1000
         IF ( num**2.GT.1000 ) GOTO 20
10    CONTINUE
20    result = num

Unfortunately, this is wrong: this finds the smallest number such that its square is larger than one thousand. The following is a correct solution:

      DO 10 num=1,1000
         IF ( num**2.GT.1000 ) GOTO 20
10    CONTINUE
20    result = num-1

but this is also correct:

      DO 10 num=1,1000
         IF ( (num+1)**2.GT.1000 ) GOTO 20
10    CONTINUE
20    result = num

Implied DO loops

The so-called implied do loops do not contain the keyword DO, and they fit on one line, eg

      WRITE (*,*) (a(i),i=2,top,2)

More about this in the section on arrays.

WHILE Loop

If a loop has to run until some condition is met, irrespective the number of iterations, it is natural to put that condition instead of the iteration information in the DO statement:

      DO WHILE (condition)
         ...
      END DO

where condition is any logical expression.

Some compilers provide this as an extension of the Fortran77 standard, and it is an official part of Fortran90. In Fortran90, one could also use an unbounded DO loop and an EXIT statement:

      DO
         IF (.NOT. the above condition) EXIT
         ...
      END DO

Example of a WHILE loop

Consider the problem of reading a number of time values from the user and computing their average. Since times have to be positive, we will take a negative value to mean that the user has finished typing in value.

      sum = 0.
      nval = 0
      READ *,tval
      DO WHILE (tval.GE.0)
         sum = sum+tval
         nval = nval+1
         READ *,tval
      END DO

This reads values; next we compute their average, keeping in mind that the number of valid inputs may have been zero:

      IF (nval.EQ.0) THEN
         average = 0.
      ELSE
         average = sum/nval
      END IF

This algorithm is correct, but there is a slight lack of elegance in the duplication of the READ statement: if you ever make a change in one statement, you must remember to change the other statement too. An implementation that does not have this defect, and that is closer to what you conceptually wanted to achieve uses a GOTO instruction to simulate the loop and another to effect the termination of the loop:

10    CONTINUE
         READ *,tval
         IF (tval.LT.0) GOTO 20
         sum=sum+tval
         nval = nval+1
      GOTO 10
20    CONTINUE

In Fortran90 the jumps can be eliminated:

      DO
         READ *,tval
         IF (tval.LT.0) EXIT
         sum=sum+tval
         nval = nval+1
      END DO

Exercise: write a loop that reads positive integers from the user, and computes the sum of the even ones. Do this both in Fortran77 and Fortran90.