Exercises: Control Flow

This is your first chapter where every exercise can be run. Do exactly that: predict the output on paper first, then compile with gfortran -std=f2018 -Wall and check yourself. The gap between your prediction and the machine's answer is where the learning is — and if they ever disagree and you are sure you are right, you may have found a bug, which is a good day.

Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†) and odd-numbered problems are in appendices/answers-to-selected.md; the four computational ones (4.7, 4.9, 4.18, 4.26) also ship as compilable code in code/exercise-solutions.f90. Try every problem before you look.


Part A — Warm-ups ⭐

4.1 † Predict the exact output of this snippet, then run it:

integer :: n = 7
if (n < 0) then
  print '(a)', 'negative'
else if (n == 0) then
  print '(a)', 'zero'
else if (n < 10) then
  print '(a)', 'small positive'
else
  print '(a)', 'large'
end if

4.2 List the six relational operators, and write how Fortran spells logical AND, OR, and NOT. How do these spellings differ from Python and from C?

4.3 † Give the two reasons the expression in a select case may be an integer or character but never a real.

4.4 How many times does do i = 1, 10, 2 execute its body, and what values does i take? What about do i = 10, 1, -3?

4.5 † In one sentence each, state the difference between exit and cycle, and between a bare exit and exit outer (where outer names a loop).

4.6 True or false, with one sentence of justification: "A bare exit inside the inner of two nested loops terminates both loops."


Part B — Type, Compile, and Run ⭐⭐

Predict the output first; then compile and run.

4.7 † Write a program that prints the numbers 1 through 15, one per line, except that multiples of 3 print Fizz, multiples of 5 print Buzz, and multiples of 15 print FizzBuzz. (Which condition must you test first, and why?)

4.8 Write a counted do loop that computes and prints $8!$ (eight factorial). Use an integer accumulator initialized to 1. What is the largest factorial that fits in a default 32-bit integer, roughly — and how would you find out? (Recall huge from Chapter 3.)

4.9 † Using a do while loop, count how many steps the Collatz sequence takes to reach 1 starting from $n = 6$ (rule: if $n$ is even, $n \leftarrow n/2$; if odd, $n \leftarrow 3n+1$). Predict the count by hand, then confirm.

4.10 Write a program that maps an integer 1–7 to a weekday name (1 -> Monday, …, 7 -> Sunday) with a select case, and prints invalid via case default for anything else. Test it on 3, 7, and 9.


Part C — Port It ⭐⭐

Translate the given code to idiomatic modern Fortran. Match the output, then note one difference between the languages that the port made you confront.

4.11 † Port this Python loop:

total = 0
i = 1
while total < 100:
    total += i
    i += 1
print(i - 1, total)

4.12 Port this Python loop, which sums the numbers 1–20 but skips multiples of 4, using cycle:

s = 0
for k in range(1, 21):
    if k % 4 == 0:
        continue
    s += k
print(s)

4.13 † Port this C switch, and explain what the deliberate missing break does — and how your Fortran version, which cannot fall through, must be written to reproduce it:

switch (grade) {
  case 'A':
  case 'B': printf("pass with merit\n"); break;
  case 'C': printf("pass\n"); break;
  default:  printf("see instructor\n");
}

Part D — Find the Bug ⭐⭐

Each compiles-or-runs wrongly. Diagnose it, then fix it.

4.14 † This is meant to stop when x reaches exactly 1.0, but it loops far longer (or forever). Why?

real(dp) :: x = 0.0_dp
do
  x = x + 0.1_dp
  if (x == 1.0_dp) exit
end do

4.15 This should stop scanning the entire table at the first zero, but it only stops the current row. Fix it.

do row = 1, nrows
  do col = 1, ncols
    if (a(col, row) == 0) exit
  end do
end do

4.16 † This select case will not compile. Why, and how do you fix it?

select case (score)
case (0:60)
  print '(a)', 'fail'
case (60:100)
  print '(a)', 'pass'
end select

4.17 This do concurrent gives wrong or nondeterministic results when the compiler actually parallelizes it. Explain, and rewrite it as an ordinary do loop.

do concurrent (i = 2:n)
  a(i) = a(i) + a(i-1)
end do

Part E — Design It (Extend the Heat Solver) ⭐⭐

4.18 † Extend the Chapter 4 checkpoint. Before the time loop, add a select case on an integer, parameter :: bc_type that prints which boundary condition is active — 1 Dirichlet, 2 Neumann, 3 periodic, anything else UNKNOWN. Loop bc_type over 0–3 to show all four branches.

4.19 Modify the skeleton so that, instead of reporting every step, it prints its boundary/interior counts only every third step, using mod(step, 3) == 0 (with an if, or with a cycle). Run it with n_steps = 9 and predict which steps report.

4.20 † Add an early-exit to the time loop: introduce a logical :: converged (set it .true. when step reaches some n_settle, a placeholder for the real convergence test of Chapter 24), and exit the named time loop when it becomes true. Print how many steps actually ran.


Part F — Back of the Envelope ⭐⭐⭐

Order-of-magnitude reasoning; show your work.

4.21 † The heat solver sweeps an $n \times n$ interior grid every time step, doing a fixed amount of work per interior point. For $n = 1000$ and $10^5$ time steps, roughly how many interior-point updates does a full run perform? At $10^{9}$ updates per second on one core, how long is that, and why does it push you toward Part VIII?

4.22 Suppose a 12-way dispatch is written as an if-chain of 12 equally-likely conditions tested in order. On average, how many conditions are evaluated per call? If select case compiles instead to a single jump table, how many? What does this say about the value of expressing intent precisely?

4.23 † A residual r starts at $r = 1.0$ and is halved each iteration of a do while (r > tol) loop. About how many iterations run before it drops to tol $= 10^{-6}$? (Estimate with $\log_2$; you do not need a computer.)


Part G — Interleaved (Chapters 2–3) ⭐⭐

4.24 † (Chapter 3 — integer division.) This loop is supposed to print the reciprocals $1/i$ as reals, but prints 1.000 then all zeros. Diagnose and fix:

real(dp) :: r
integer  :: i
do i = 1, 5
  r = 1 / i
  print '(f6.3)', r
end do

4.25 (Chapter 2 — flags.) Which gfortran flag makes an out-of-bounds array access inside a loop abort with a helpful message instead of silently reading garbage, and why do you compile with it during development but not in a production run?

4.26 † (Chapter 3 — logical operators and precedence.) A year is a leap year when it is divisible by 4 and not by 100, or it is divisible by 400. Write the single logical expression for leap, and evaluate it by hand for 1900, 2000, 2024, and 2023. Does .and. bind tighter than .or.? Do you need parentheses?

4.27 (Chapter 3 — mixed mode.) Rewrite the condition if (n / 2 > 0.5) — where n is an integer — so it does what the author almost certainly intended (is n at least 1?), and explain what the original actually tests.

4.28 † Type, compile, and run code/project-checkpoint.f90. Then change nx, ny from 4, 3 to 5, 5 and, before recompiling, predict the new boundary= and interior= counts. Confirm.


Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md. The setup problem 4.28 has an answer you produce at the keyboard; the appendix gives the predicted counts to check against.