Exercises: Reading FORTRAN 77
These exercises build the one skill this chapter exists to teach: reading old code fluently, and mapping every legacy construct to its modern replacement. Because Part IV is about reading before rewriting, many problems hand you real fixed-form fragments and ask you to decode them; the "Modernize it" section (Part E) is the largest, because turning a legacy idiom into clean modern Fortran is the move you will make a thousand times.
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†)
and odd-numbered problems are in appendices/answers-to-selected.md; the compilable ones are in this
chapter's code/exercise-solutions.f90. Try every problem before you look. Compile legacy fragments with
gfortran -std=legacy file.f and modern code with gfortran -std=f2018 -Wall file.f90.
Part A — Warm-ups ⭐
17.1 † In fixed-form source, name what belongs in columns 1–5, column 6, columns 7–72, and column 1
when it holds a C.
17.2 State the implicit-typing rule in one sentence: which first letters make a variable INTEGER,
and which make it REAL?
17.3 † What does -std=legacy tell gfortran to do, and when do you need it?
17.4 In one sentence each, say what COMMON, EQUIVALENCE, and BLOCK DATA are for.
17.5 † Why can a named COMMON block be initialized only in a BLOCK DATA unit and not in an ordinary
subroutine?
17.6 Translate the Hollerith constant 11HTEMPERATURE into a modern quoted character string, and say
how many characters follow the H.
Part B — Read the Columns ⭐⭐
17.7 † The following line is meant to be a comment, but it does not compile as one. Why, and how do you fix it?
C THIS IS A COMMENT
17.8 Here is a continued statement. Rewrite it as a single modern free-form line, and identify the continuation character and the column it sits in.
AREA = LENGTH * WIDTH
+ * DEPTH
17.9 † A programmer pastes a working fixed-form statement into columns 1–66 of a new file (shifting it six columns left so it starts in column 1). It no longer compiles. Explain exactly what went wrong.
17.10 What is printed by this fixed-form program? Trace it by hand, then check.
PROGRAM COUNT
K = 0
DO 10 I = 1, 5
K = K + I
10 CONTINUE
WRITE (*,*) K
STOP
END
Part C — COMMON and EQUIVALENCE ⭐⭐
17.11 † Two routines declare COMMON /BLK/ A, B, N and COMMON /BLK/ X, Y, M respectively (A,B,X,Y
real; N,M integer). Do they share data correctly? Which variable in the second routine corresponds to
B in the first, and why does the compiler not check this?
17.12 Given DIMENSION G(4,4) and EQUIVALENCE (G(1,1), V(1)) with DIMENSION V(16), which element
of G is V(6)? (Fortran is column-major.)
17.13 † A legacy routine contains EQUIVALENCE (RBUF(1), IBUF(1)) where RBUF is REAL and IBUF is
INTEGER. Name two distinct hazards this creates, and give the modern intrinsic that expresses the safe
version of the "reinterpret the bits" intent.
17.14 Explain why EQUIVALENCE can reduce a program's speed, in terms of what it tells (or hides
from) the optimizer.
Part D — Decode the Control Flow ⭐⭐
17.15 † Rewrite this computed GOTO as a modern select case, including a default.
GO TO (100, 200, 300), MODE
17.16 Decode this arithmetic IF and rewrite it as a modern if … else if … end if.
IF (X - XCRIT) 10, 20, 30
17.17 † Here is a statement function. Say what it computes, then rewrite it as a modern internal pure
function with intent.
HYP(A, B) = SQRT(A*A + B*B)
17.18 This fixed-form loop uses a label and a GO TO. Rewrite it as a modern do … end do with
exit, and explain why the modern version needs no label.
N = 0
50 N = N + 1
IF (2**N .LT. 1000) GO TO 50
WRITE (*,*) N
Part E — Modernize It ⭐⭐ / ⭐⭐⭐
Rewrite each legacy fragment in modern style: implicit none, free-form, explicit types with a dp kind,
intent, modules for shared state, structured control. State any assumption you must make.
17.19 † Modernize this shared-state pattern into a module.
COMMON /MESH/ U(100), NPTS
REAL U
INTEGER NPTS
17.20 Modernize this subroutine header and its implicit-typed body. (Assume SUM should accumulate a
real total over the integer-indexed array A of length N.)
SUBROUTINE TOTAL(A, N, S)
DIMENSION A(N)
S = 0.0
DO 10 I = 1, N
S = S + A(I)
10 CONTINUE
RETURN
END
17.21 † Modernize the BLOCK DATA unit below into the modern equivalent.
BLOCK DATA INIT
COMMON /CFG/ DT, NSTEP
DATA DT /0.01/, NSTEP /500/
END
17.22 Modernize this fixed-form convergence loop (labels, GO TO, statement function) into structured
modern Fortran. Keep the same behavior.
F(X) = COS(X)
XOLD = 0.0
100 XNEW = F(XOLD)
IF (ABS(XNEW - XOLD) .LT. 1.0E-6) GO TO 200
XOLD = XNEW
GO TO 100
200 WRITE (*,*) XNEW
17.23 † ⭐⭐⭐ A legacy routine finds the peak temperature of a field by aliasing the 2-D array T as a
1-D vector with EQUIVALENCE (T(1,1), TFLAT(1)) and scanning TFLAT in a DO loop. Rewrite the
peak-finding in modern Fortran with no EQUIVALENCE. What single intrinsic call replaces the entire loop?
17.24 ⭐⭐⭐ Take the PLATE SETBC subroutine from §17.6 — it zeros the field, sets the three cold
edges, and makes the top edge hot — and rewrite it in modern Fortran: implicit none, a dp kind, an
assumed-shape (or module) field, no COMMON, no DATA, no labels. You do not have to compile it against
the rest of the solver — just produce a clean, correct modern setbc.
Part F — Find the Bug ⭐⭐
17.25 † This fixed-form routine is supposed to average an array of fractional values (each between 0
and 1) but prints 0.0 every time. Find the bug — there are actually two implicit-typing mistakes here.
(Hint: look at the first letters of the variable names.)
SUBROUTINE MEAN(A, N)
DIMENSION A(N)
NSUM = 0.0
DO 10 I = 1, N
NSUM = NSUM + A(I)
10 CONTINUE
AV = NSUM / N
WRITE (*,*) AV
RETURN
END
17.26 A programmer "modernizes" PLATE by deleting the separate TNEW array in RELAX, reasoning it
wastes memory, and updates T in place — each new interior value overwriting T(I,J) and then being used
by the next cell in the same sweep. The program still runs and still "converges." What silently changed,
and is the result still a Jacobi solution? (Name the method the edited code now implements.)
Part G — Read, Estimate, Interleave ⭐⭐⭐
17.27 † (Back of the envelope.) The PLATE kernel keeps two full-grid arrays — the field T and its
sweep copy TNEW — each N × N (8 bytes per value, since it is DOUBLE PRECISION). For a production run
at N = 2000, estimate the memory footprint of the two arrays. If Jacobi needs roughly $O(N^2)$ iterations
to converge on an $N \times N$ grid, how does the total work scale with N, and why does this motivate the
better methods and parallelism of later parts?
17.28 (Port it.) Here is the Jacobi interior update in Python/NumPy. Port it to a modern Fortran
do concurrent (or nested do) loop over the interior, and say in one sentence why the Fortran version is
expected to be far faster for a large grid than the pure-Python element-by-element version would be.
for i in range(1, nx-1):
for j in range(1, ny-1):
t[i][j] = 0.25 * (told[i-1][j] + told[i+1][j] + told[i][j-1] + told[i][j+1])
17.29 † (Interleaved — Ch. 4.) The PLATE RELAX loop exits on either convergence or the
iteration cap. Using the named-loop and exit ideas from
Chapter 4, write the modern loop so a single
do construct handles both exits cleanly, and add a post-loop if that reports which one fired.
17.30 (Interleaved — Ch. 8.) PLATE declares COMMON /GRID/ identically in four routines. Sketch
the modern module hierarchy that replaces it (name the module, its public entities, and which routines
use it), and state the one compile-order rule from
Chapter 8 that the module version must
obey.
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the
compilable modernizations (17.20, 17.22, 17.23, 17.24, 17.28) are worked in code/exercise-solutions.f90.
Where a problem asks you to modernize a fragment in isolation, any clean, correct, compilable modern
version that preserves the behavior is a valid answer — there is rarely a single "right" rewrite.