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.