Exercises: FORTRAN 77 to Modern Fortran
This is a translation chapter, so most of these exercises are translations: you are handed a fragment of FORTRAN 77 and asked to produce its faithful modern equivalent. Do the work the way you would on the job — identify which dictionary entry applies, translate every clause, and (where the fragment computes something) confirm the numbers are unchanged. Type and compile the modern versions; the compiler is your regression test for anything that would not build.
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†)
and odd-numbered problems are in appendices/answers-to-selected.md; the computational ones (19.5, 19.9,
19.11, 19.13, 19.19) are also provided as compilable code in code/exercise-solutions.f90. Predict every
output before you compile.
Throughout, "modern" means: free-form, implicit none, lowercase keywords, real(dp) reals with
use, intrinsic :: iso_fortran_env, only: dp => real64, intent on every dummy argument, and modules or
internal procedures rather than COMMON and external routines. The running FORTRAN 77 program is PLATE
(code/plate-legacy.f); several problems draw on it.
Part A — Name the Move ⭐
19.1 † For each FORTRAN 77 construct, name its standard modern-Fortran replacement in one phrase:
(a) COMMON /STATE/ X, Y, N; (b) EQUIVALENCE (R, IR) used to inspect the bits of a real; (c) a statement
function SQ(X) = X*X; (d) GO TO (10, 20, 30), MODE; (e) a dummy argument declared REAL A(*).
19.2 True or false, with one sentence of justification: "Fixed-form source has been removed from the
Fortran standard, so a .f file will no longer compile."
19.3 True or false, with one sentence: "Adding implicit none to a working legacy routine is a pure style
change and cannot affect whether the program is correct."
19.4 Give the symbolic modern operator for each: .LT., .GE., .NE., .EQ., .LE.. Which two logical
operators from the set .AND. .OR. .NOT. .EQV. .NEQV. have no symbolic form?
Part B — Storage and Data ⭐⭐
19.5 † Modernize it. Rewrite this shared state as a module, and show a two-line driver that sets the grid and prints the cell count. (Code solution provided.)
SUBROUTINE SETGRD
COMMON /GRID/ NX, NY
NX = 41
NY = 41
END
19.6 Modernize it. A BLOCK DATA unit initializes a COMMON block. Rewrite both as a single module
with an initialized module variable, and explain in one sentence why the BLOCK DATA unit disappears.
BLOCK DATA PHYS
COMMON /CONST/ PI, TWOPI
DATA PI, TWOPI /3.14159265, 6.28318531/
END
19.7 † Find the bug. Two routines share /GRID/, but one of them reads it wrong. State the symptom,
say which word each routine actually reads, and explain why re-expressing /GRID/ as a module makes the bug
impossible.
SUBROUTINE SETGRD
COMMON /GRID/ NX, NY
NX = 200
NY = 100
END
SUBROUTINE NCELLS(N)
COMMON /GRID/ NCELL
N = NCELL
END
19.8 For each use of EQUIVALENCE, name the correct modern replacement (transfer, a derived type, or
separate allocatable arrays), and say why the other two would be wrong:
(a) overlaying a scratch buffer on an unused array to save memory; (b) viewing a REAL as an INTEGER to
extract its exponent bits; (c) naming the three components of a coordinate triple so P(1), P(2), P(3)
and X, Y, Z refer to the same storage.
Part C — Control Flow ⭐⭐
19.9 † Modernize it. Translate this GOTO relaxation loop to a structured do/exit loop, preserving
both clauses of the exit test. Then say what breaks if you translate it to do while (diff > tol) alone.
(Code solution provided.)
ITER = 0
10 ITER = ITER + 1
CALL SWEEP(DIFF)
IF (DIFF .GT. TOL .AND. ITER .LT. MAXIT) GO TO 10
19.10 Modernize it. Rewrite this computed GOTO as a select case, and add the guard the original
lacked.
GO TO (100, 200, 300), MODE
100 CALL EXPLICIT
GO TO 900
200 CALL IMPLICIT
GO TO 900
300 CALL CRANK
900 CONTINUE
19.11 † Modernize it. Translate this arithmetic IF (on a real residual) to a block if. Then explain
which branch is fragile and how you would change it if the "zero" case is meant to catch "negligibly small."
(Code solution provided.)
IF (RESID) 10, 20, 30
10 SGN = -1.0
GO TO 40
20 SGN = 0.0
GO TO 40
30 SGN = 1.0
40 CONTINUE
19.12 Find the bug. A modernizer rewrote a GOTO loop as the do while below and the program now hangs
on a hard problem. What did the translation drop, and what is the one-clause fix?
iter = 0
do while (diff > tol)
iter = iter + 1
call sweep(diff)
end do
Part D — Procedures ⭐⭐
19.13 † Modernize it. Turn this statement function into an internal pure function with declared types
and an intent. Why must it be translated to compile under -std=f2018? (Code solution provided.)
AVG(TL, TR, TB, TA) = 0.25 * (TL + TR + TB + TA)
19.14 Modernize it. Rewrite this assumed-size print routine with an assumed-shape argument, and note exactly which argument disappears and why.
SUBROUTINE PRTROW(ROW, NVAL)
REAL ROW(*)
INTEGER NVAL, K
WRITE (*, '(100F6.1)') (ROW(K), K = 1, NVAL)
END
19.15 † Add intent. Give the correct intent for each dummy argument of the legacy routine below,
having inferred the data flow from the body, and rewrite its header in modern form (assumed-shape, real(dp)).
SUBROUTINE SETEDGE(T, NX, NY, THOT, TCOLD)
REAL T(NX, NY), THOT, TCOLD
INTEGER NX, NY, I, J
DO 10 I = 1, NX
T(I, NY) = THOT
10 CONTINUE
DO 20 J = 1, NY
T(1, J) = TCOLD
T(NX, J) = TCOLD
20 CONTINUE
END
Part E — Form and Typing ⭐⭐
19.16 † Convert the form. Rewrite this fixed-form fragment in free-form: turn the C comment into !,
the column-6 continuation into a trailing &, and drop the label if it is unreferenced.
C ACCUMULATE THE WEIGHTED SUM
S = W1*X1 + W2*X2 +
& W3*X3 + W4*X4
19.17 Add implicit none. Declare every variable in this implicitly-typed fragment with the type
implicit typing would have given it, and add implicit none. Which variables were integer, and which real?
SUM = 0.0
DO 10 K = 1, N
SUM = SUM + A(K)
10 CONTINUE
XBAR = SUM / N
19.18 † Find the bug. This fragment compiles under FORTRAN 77 and prints a wrong answer; under
implicit none it will not compile at all. Identify the defect and explain how implicit none exposes it.
TOTAL = 0.0
DO 10 I = 1, N
TOTAL = TOATL + X(I)
10 CONTINUE
WRITE (*,*) TOTAL
Part F — Modernize PLATE / Design ⭐⭐/⭐⭐⭐
19.19 † Design it (PLATE). Finish modernizing the PLATE edge-setter: write a modern set_edges
subroutine (assumed-shape, intent, real(dp)) that sets the top edge hot and the other three cold, using
array-section assignments rather than loops. Confirm by hand that a cold corner reads 0.00.
(Code solution provided.)
19.20 Design it (PLATE). Bundle PLATE's COMMON /GRID/ (the field T and the size N) into a
single field_t derived type (n and an allocatable t(:,:)), and rewrite the relax signature to take one
type(field_t) argument. What did the derived type buy you over separate module variables?
(See Chapter 9.)
19.21 † Modernize it (PLATE). Rewrite PLATE's reporting section — the two WRITE/FORMAT pairs and
the CALL PRTROW(T(1,JC), NX) — in modern Fortran: an inline format on the print/write, an assumed-shape
row, and no assumed-size sequence-association trick. Keep the printed values identical.
19.22 Back of the envelope. The legacy PLATE declares T and TNEW as
DOUBLE PRECISION (NMAX,NMAX) with NMAX = 21, but runs an N = 4 grid. (a) How many double precision
cells are allocated per array, and how many does the 4×4 run use? (b) At 8 bytes per double precision,
how many bytes per array are wasted? (c) What does switching to allocatable (sized 4×4) save across the two
arrays, and what limit does it also remove?
Part G — Interleaved and Deeper ⭐⭐⭐
19.23 † Interleaved (Chapter 8).
Explain why converting COMMON to a module is a prerequisite for safely parallelizing a kernel across
OpenMP threads or coarray images — not just a tidiness improvement. What specifically goes wrong if global
state stays in COMMON?
19.24 Interleaved (Chapter 4). Rewrite
this nested DO with a shared CONTINUE terminator as modern nested loops with one end do each, then
collapse the whole thing to a single whole-array statement.
DO 10 J = 1, NY
DO 10 I = 1, NX
T(I,J) = 0.0
10 CONTINUE
19.25 † Interleaved (Ch. 17–18). A legacy routine contains both a statement function and an assumed-size
array argument. Which one must be translated for the routine to compile under -std=f2018, and which is only
advisable? Justify each from the construct's standard status.
19.26 Port it. Translate this Python loop (a while with a break) to a Fortran do/exit loop.
Note which Fortran keyword plays the role of Python's break.
n = 0
while True:
n += 1
r = residual()
if r < tol or n >= max_iter:
break
19.27 † Find the bug. A modernization changed every REAL to real(dp) but left one real literal
untouched, as shown. What subtle change did this introduce, and what is the fix? (Preview of
Chapter 20.)
real(dp) :: alpha
! ...
alpha = 0.1 ! a default-real literal, assigned to a real(dp) variable
19.28 Back of the envelope. Skim the quick-reference table (§19.5) and the PLATE program. Of the
roughly fifteen dictionary rows, estimate how many apply to a typical 200-line FORTRAN 77 numerical kernel,
and argue which single translation removes the most risk per line changed. Defend your choice.
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the
computational ones (19.5, 19.9, 19.11, 19.13, 19.19) are compilable in code/exercise-solutions.f90. The
"find the bug" problems (19.7, 19.12, 19.18, 19.27) are worth extra attention: each is a real mistake made
during a real modernization, and learning to see them is most of the skill this chapter teaches.