Chapter 7 — Key Takeaways (I/O and Formatted Output)

A one-page reference for reading data in, writing results out, and controlling exactly how they look.

The core edit descriptors

Descriptor What it does Example → output
iw integer, right-justified in width w i6 of 42····42
i0 integer, minimal width i0 of 4242
iw.m integer, ≥ m digits (zero-padded) i5.3 of 7··007
fw.d fixed-point real, d decimals, width w f8.2 of 3.14159····3.14
ew.d scientific, 0.xxxxE±ee form e12.4 of 1e-4··0.1000E-03
esw.d scientific, mantissa 1–9 (physicist's form) es12.4 of 1e-4··1.0000E-04
a / aw character string (as-is / in width w) a of 'hi'hi
nx insert n blanks (consumes no value) 3x···
/ end this record, start a new line
*(...) unlimited repeat (F2008): apply to all items '(*(f8.2))' writes a whole row

(dots mark spaces.) The grammar to memorize: w.d = field width . digits after the point.

Two rules that bite

  • Overflow → asterisks. A value too wide for its field prints as ******, never truncated. Widen the field or switch to es. A column of asterisks means "too narrow," not "bad data."
  • Formatted vs list-directed leading space. print '(a)', x writes no leading space; print *, x writes one leading blank (the carriage-control ghost). Use formatted when the first column matters.

List-directed vs formatted — which to use

Use list-directed (*) Use a format '(...)'
Quick debug dumps, throwaway messages Anything a human or program will read
Reading a few values you typed Tables, aligned columns, files with a fixed layout
You do not care about spacing/precision You need exact width, decimals, alignment

The file toolkit

open(newunit=u, file='out.dat', status='replace', action='write', iostat=ios, iomsg=msg)
write(u, '(f8.2)') x          ! or  write(u, *) x   (list-directed)
read(u, *) y                  ! read one value list-directed
close(u)
inquire(file='out.dat', exist=ok)     ! ask before you open
  • newunit=u — runtime picks a free unit; never hard-code open(17, ...).
  • status='replace' (create/overwrite), 'old' (must exist), 'new' (must not), 'scratch' (temporary, auto-deleted).
  • action='read', 'write', 'readwrite'.
  • Sequential (default) = records in order. Direct = access='direct', recl=N, address by rec=k; recl units are processor-defined (bytes in gfortran — use inquire(iolength=n) for portability).

namelist — key/value configuration

namelist /config/ nx, ny, alpha, dt, n_steps
read(u, nml=config)     ! parses a  &config nx=100, alpha=1e-4 /  block, by name
write(u, nml=config)    ! dumps the group back out (provenance / restart)

Names match case-insensitively, in any order; omitted names keep their prior (default) value. The file group opens with &config and closes with /. Always read with iostat so a typo'd key or group name fails loudly.

Text vs binary — the decision that matters at scale

Text (f, es) Unformatted Stream
Human-readable yes no no
Exact (no precision loss) no yes yes
Fast / compact in bulk no yes yes
Record markers n/a yes (Fortran-only) none (C/Python-friendly)
Self-describing no no no → use HDF5/NetCDF (Ch. 25)
open(newunit=u, file='f.bin', form='unformatted', access='stream', status='replace', action='write')
write(u) field          ! whole array, exact bytes, one statement

Rule: text for humans and small data; binary for machines and big data.

Error handling: iostat / iomsg

read(u, *, iostat=ios) x
if (ios == iostat_end) exit        ! iostat_end from iso_fortran_env — NOT -1
if (ios /= 0) then ...             ! positive = real error; iomsg = the message

iostat: 0 = success, negative = end-of-file/record (iostat_end, iostat_eor), positive = error. iomsg fills a string with the description. Guard every open; loop reads until iostat_end.

Pitfalls

  • Hard-coding -1 for end-of-file → use iostat_end.
  • Reading a string with spaces list-directed (stops at the first blank) → read with '(a)'.
  • Field too narrow → asterisks (widen it).
  • Direct-access recl in the wrong units → non-portable file.
  • Namelist group-name/key typo with no iostat → silently ignored.

Compile flags introduced

None. I/O needs no special flags: gfortran -std=f2018 -Wall -O2 file.f90 handles all of it.

Numbers & rules worth memorizing

  • f8.2 → 8 wide, 2 decimals, right-justified. Predict any line to the column from w.d.
  • A real(dp) is 8 bytes as binary; ~24 bytes as full-precision text — binary is ~3× smaller and ~100× faster to write in bulk (illustrative orders of magnitude).
  • newunit for units; iostat_end for EOF; &group … / for namelist.

Heat-solver piece added this chapter

The solver stops hard-coding its parameters: it reads nx, ny, alpha, dt, n_steps from a namelist file (heat.nml) and writes the field to a text file via write_field(field, filename) (assumed-shape array, deferred-length filename, '(*(f8.2))' per row). These become the heat_io module in Chapter 8; write_field grows a write_vtk sibling in Chapter 26.