Chapter 18 — Key Takeaways (Modernizing Legacy Fortran)
A one-page reference for taking FORTRAN 77 to modern Fortran without changing the answer.
The eight-step recipe (in order)
| # | Step | Legacy → Modern | Home chapter |
|---|---|---|---|
| 1 | Add implicit none |
implicit typing → declare everything | Ch. 2 |
| 2 | Free-form source | fixed columns → free-form (findent/fprettify) |
Ch. 2 |
| 3 | COMMON → modules |
memory overlay → typed, checked namespace | Ch. 8 |
| 4 | Add intent |
silent inout → intent(in/out/inout) |
Ch. 6 |
| 5 | Assumed-size → assumed-shape | a(*)/a(n) → a(:); size travels with the array |
Ch. 6 |
| 6 | GOTO → structured control |
GOTO/arith. IF → do/exit/cycle/if/select case |
Ch. 4 |
| 7 | EQUIVALENCE → types/transfer |
memory aliasing → maxval/reshape, transfer, derived types |
Ch. 9 |
| 8 | Add error handling | silent failure → error stop, stat=, iostat=, validation |
Ch. 13 |
Rule of thumb: safest/most-mechanical first (1, 2, 6), structural (interface-changing) next (3, 4, 5),
EQUIVALENCE case-by-case (7), error handling last (8, easiest once the code is clean).
The three core terms
| Term | One-line meaning |
|---|---|
| Incremental modernization | Small, individually verified changes; the code compiles and passes its tests at every step. |
| Regression test | Compare output to a golden reference from the trusted original; fail if it moves beyond tolerance. |
| Numerical equivalence | Same results — bit-for-bit (arithmetic preserved) or close enough (a stated tolerance). |
| Characterization test | A test that pins current behavior (not intended behavior) so you can refactor underneath it. |
Bit-for-bit vs "close enough"
| Demand bit-for-bit when… | Fall back to tolerance when… |
|---|---|
implicit none, free-form, COMMON→module |
precision changed (real → real(dp)) |
intent, assumed-shape added |
a reduction/summation reordered |
GOTO→do (arithmetic untouched) |
-ffast-math/FMA contraction enabled, or a different compiler |
Bit-for-bit is a property of source plus compiler plus flags — pin them if you promise it.
Compare numbers within tolerance, never diff text (format/whitespace ≠ numerical difference).
Modern replacements at a glance
! COMMON -> module
module grid_data
implicit none
integer, parameter :: dp = selected_real_kind(15, 307)
real(dp) :: t(21,21)
integer :: n
end module grid_data
! GOTO convergence loop -> do / exit (exit = negation of the legacy continue test)
do
iters = iters + 1
! ... one sweep, compute dmax ...
if (dmax <= tol .or. iters >= maxit) exit
end do
! assumed-size + no intent -> assumed-shape + intent
subroutine relax(t, tol, maxit, iters)
real(dp), intent(inout) :: t(:,:)
real(dp), intent(in) :: tol
integer, intent(in) :: maxit
integer, intent(out) :: iters
! EQUIVALENCE (2-D aliased as 1-D, for a max) -> array intrinsic
tmax = maxval(t(1:n, 1:n))
! EQUIVALENCE (reinterpret bits) -> transfer, explicitly
bits = transfer(1.0_real32, 0_int32) ! = 1065353216
! error handling
if (n < 3) error stop 'grid must be at least 3x3'
The PLATE result (memorize)
- 4×4 grid, top edge 100°, three edges 0° → interior settles to 37.5° (upper), 12.5° (lower), from $3a-b=100$ and $a=3b$ ⇒ $8b=100$.
- Jacobi from a zero interior: max change is 25 after sweep 1, then halves each sweep, first below $10^{-6}$ on sweep 25.
- All iterates are exact dyadic doubles → the modern version reproduces the legacy one exactly (a bit-for-bit, indeed rounding-free, migration).
Compile flags introduced
| Flag | Use |
|---|---|
gfortran -std=legacy file.f |
build the fixed-form FORTRAN 77 "before" |
gfortran -std=f2018 -Wall file.f90 |
build the modern "after" |
-fcheck=all |
run-time bounds checking (enabled by assumed-shape) |
The ethic
Never rewrite what you can refactor. A rewrite discards the one irreplaceable asset — validated behavior — and re-earns it only at the very end, if ever. Refactoring keeps it the whole time. Improve the engineering; preserve the science; prove it with a regression test.
Project piece advanced this chapter
The Part IV side quest: apply steps 1 (implicit none) and 3 (COMMON → module) to the F77 PLATE
kernel, producing project-checkpoint.f90 — which still prints the identical 37.5 / 12.5 field in 25
sweeps. Chapter 19 finishes the remaining steps with
the translation dictionary.