Program statements are usually executed in a linear fashion. `Control structures' is the term we use for language constructs that change this linear execution.
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.
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.
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.
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.
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.
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:
a.EQ.0.0 then this is not a second degree polynomial
b**2-4*a*c.LT.0.0 then there are no real roots
b**2-4*a*c.EQ.0.0 then the two roots are equal
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.
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.
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.
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.
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.
It is considered good programming practice to let the final statement
be the CONTINUE statement:
DO 10 i=2,15statements10 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,15statementsEND DO
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
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.
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.
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
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.
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
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.