Exercises: I/O — Reading Data, Writing Results, and Formatted Output
These exercises make you predict output to the column, build real file and namelist I/O, and reason about when text is the wrong format. Do the format-prediction problems on paper first — the ability to see a formatted line in your head before you compile is the core skill of §7.1 — then check by compiling.
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 also appear as
runnable code in code/exercise-solutions.f90. Every code answer ships with a hand-computed expected output —
compile it and confirm. Try every problem before you look.
Part A — Predict the Output ⭐
Write exactly what each statement prints, marking every space with a visible box or dot. Assume real(dp)
literals and that no leading carriage-control space is added (formatted output).
7.1 † print '(a, i0)', 'n = ', 7
7.2 print '(a, i4)', 'n = ', 7
7.3 † print '(f6.2)', 3.14159_dp
7.4 print '(es10.2)', 6.02e23_dp
7.5 † print '(a, 3x, a)', 'A', 'B'
7.6 print '(2f6.1)', 1.5_dp, -2.5_dp
Part B — Type, Compile, and Run ⭐⭐
7.7 † Write a program that writes the integers 1 through 5, one per line, to nums.txt using the i0
descriptor. Then reopen the file and read them back with an end-of-file loop (§7.6), summing as you go.
Print the sum. (You should get 15.)
7.8 Declare a namelist group /grid/ nx, ny, spacing with real spacing and integer nx, ny, give all
three defaults, write a small grid.nml that sets only spacing, read it, and echo all three values.
Confirm nx and ny kept their defaults.
7.9 † Modify example-02-file-io.f90 so the three values are written on a single line with one
write(u, '(3f8.2)'), and read back with one read(u, *). Does the round trip still work? Why is
list-directed input indifferent to whether the numbers were on one line or three?
7.10 Write a program that uses inquire(file=..., exist=ok) to report found or missing for two
filenames: one your program has just created, and one it has not. Print the result with an l1 descriptor
and an a descriptor.
Part C — Find the Bug ⭐⭐
Each snippet compiles or runs but is wrong or fragile. Diagnose and fix it.
7.11 † A program prints **** where it expected a temperature:
print '(a, f4.2)', 'T = ', 12.5_dp
Why the asterisks, and what is the smallest field width that fixes it?
7.12 This read loop terminates on the author's laptop but runs forever on a classmate's:
do
read(u, *, iostat=ios) x
if (ios == -1) exit
total = total + x
end do
Name the portability bug and give the correct test.
7.13 † A larger program opens two different files on the same hard-coded unit:
open(10, file='out.dat', status='replace')
! ... hundreds of lines later ...
open(10, file='log.txt', status='replace')
What goes wrong, and what modern feature removes the whole class of bug?
7.14 A namelist read "does nothing" — the variable keeps its default even though the file sets it. The
file begins ¶ms and the code declares namelist /config/ ..., and the read has no iostat. Explain
both mistakes.
Part D — Port It ⭐⭐
Translate the Python to modern Fortran and compare.
7.15 † Port this to Fortran (it sums one number per line from a file):
with open('data.txt') as f:
total = sum(float(line) for line in f)
print(f'sum = {total:.2f}')
7.16 Port these two Python format calls to Fortran edit descriptors:
print(f'{x:8.3f}') # a real, 3 decimals, width 8
print(f'{n:05d}') # an integer, zero-padded to 5 digits
The second one needs a descriptor form the chapter mentions but does not dwell on — find it.
Part E — Design It (Heat-Solver I/O) ⭐⭐⭐
7.17 † Add a second namelist group /output/ out_file, write_every to the Project Checkpoint, so the
output filename (a character(len=:), allocatable or fixed-length string) and the write cadence (an integer)
are configurable. Show the two-group .nml file and the reads. Why are two groups better than cramming
everything into /config/?
7.18 Extend write_field to write a one-line header giving nx and ny before the data, and write a
matching read_field(field, filename) that reads the header, allocates field to the right shape, and
reads the values back. This is the beginning of a self-describing file.
7.19 † Write write_field_binary(field, filename) using access='stream', form='unformatted'. Then
describe, in prose, exactly how a Python script would read the file with numpy.fromfile, and name the three
things the two sides must agree on that a raw binary file does not record.
Part F — Back of the Envelope ⭐⭐⭐
Order-of-magnitude estimates. Show your reasoning; round numbers are fine and expected.
7.20 † A simulation holds a $2000 \times 2000$ field of real(dp). (a) How many bytes is it as raw
unformatted binary? (b) Taking a text representation at roughly 24 bytes per value (16 significant digits, a
sign, an exponent, a separator), how many bytes as text? (c) What is the text-to-binary size ratio?
7.21 Using the field of 7.20, suppose writing one value as text costs about 100 ns (the decimal conversion) and writing one value as binary costs about 1 ns (a bulk byte copy). Estimate the write time per timestep each way, and over a 1000-step run. What does this say about where a naive solver spends its time?
7.22 † (Interleave, Ch. 3.) Explain why print '(f8.2)', real(1/3, dp) prints 0.00, and give the
one-character-per-operand change that makes it print 0.33.
Part G — Interleaved ⭐⭐
Reaching back to earlier chapters.
7.23 (Ch. 6.) Could you mark write_field a pure subroutine? Answer yes or no and justify it from
the definition of pure.
7.24 † (Ch. 5.) write(u, '(*(f8.2))') field(:, 1) writes the first column of the field. In
Fortran's column-major layout, are the elements of field(:, 1) contiguous in memory? What about
field(1, :), a row?
7.25 (Ch. 5.) Rewrite write_field so each column of the plate becomes one line of the file instead
of each row. Which loop nesting does that require, and how does it relate to memory order?
7.26 † (Ch. 3.) Predict the output of print '(a, i5.3)', 'code = ', 7. What does the .3 do?
7.27 † Write a program that tries to open a nonexistent file with status='old', captures iostat and
iomsg, prints the message, and exits cleanly with error stop instead of crashing.
7.28 (Ch. 6.) The checkpoint's write_field declares its filename argument character(len=*). What is
that dummy-argument form called, and why is it better here than character(len=200)?
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the
computational answers (7.7, 7.20, 7.21, 7.22, 7.26) are also runnable in code/exercise-solutions.f90, each
with its hand-computed expected output. The design problems (7.17–7.19) have model solutions rather than
single correct ones.