Fortran from the ground up

Formatted output

List-directed print chooses its own widths and pads numbers with blanks. A format string says exactly how wide every value is, which is the difference between debugging output and a report.

Part 8 of 12

Intermediate16 min readBack to Courses

Two ways to write a line

print * and write(*, *) are list-directed: you hand over the values and the compiler chooses the layout. Every value gets a field wide enough for the largest value of its type, integers twelve columns and reals fifteen or so with trailing blanks, so a line of output is mostly padding.

write(*, '(...)') is formatted. The second argument is a format string, and it describes the output position by position: this field is an integer five wide, then a space, then a real with two decimals. The same write statement sends the text to the screen with * as the unit, or into a character variable when the unit is a variable.

Formatted output is more typing and less thinking, because the answer is decided before the program runs. Debug lines can stay list-directed; anything a person will read as a table should be formatted.

The descriptors worth knowing

I is an integer and takes a width: I5 right-aligns the value in five columns. I0 means "as narrow as the value needs", which is what you want in the middle of a sentence. F is a real in fixed-point notation and takes a width and a decimal count: F8.2 gives eight columns with two digits after the point, and F0.2 drops the padding.

A is text. With a width, A10 reserves ten columns; without one, A prints the value at its own length, which is the safe choice for anything whose length you have not fixed. X inserts spaces: 2X is two of them. A number before a descriptor repeats it, so 3I5 is three integers, each five wide, and 2(A,I0) is the pair twice.

List-directed output switches to scientific notation for very large or very small values, which is why a real sometimes prints with an E in it. In a format an F field that is too narrow gives a row of asterisks instead of quietly widening — a loud failure, and the better kind.

Alignment, padding and the trailing blanks

Numbers are right-aligned in their fields, which is what makes a column of figures line up on the decimal point. Text in an A field is padded out to the field width, and which side the padding lands on is not worth relying on — this runtime pads on the left. A report that has to line up everywhere gives each text column an exact width and does not depend on the padding side.

The trailing blanks of list-directed reals are the giveaway that no format was used: print *, 3.5 prints 3.50000000 followed by blanks, while write(*, '(F0.2)') 3.5 prints 3.50 and stops. Once output is going into a log that people read, that difference is the whole point.

One value, several widths

integer :: n
real :: price

n = 42
price = 3.5

print *, n, price                       ! wide fields
write(*, '(A,I0)') 'count: ', n         ! count: 42
write(*, '(A,I5)') 'count: ', n         ! count:    42
write(*, '(A,F8.2)') 'price: ', price   ! price:     3.50
write(*, '(A,F0.2)') 'price: ', price   ! price: 3.50
write(*, '(A)') repeat('-', 24)         ! a rule
write(*, '(3(A,I0))') 'a=', 1, ' b=', 2, ' c=', 3

A format is a stencil

A format string is read left to right, and each descriptor consumes the next value from the output list. A repeat count is shorthand: 3I5 is three integer fields, and 2(A,I0) is the pair twice. When the values outnumber the descriptors, the format is reused from the beginning, so one write(*, '(I3)') with five integers prints five fields on one record. A / starts a new record between groups.

Widths are a contract, and it fails loudly in one direction: a numeric field too narrow prints asterisks instead of the number, so a column full of **** is telling you the width, not the arithmetic, is wrong. A character field that is too narrow is quieter — it keeps only as many characters as fit. Check the widest value a column can hold before choosing its width.

Three formatting habits prevent confusing output: write the header and rows from the same widths, count the values against the descriptors before adding a column, and give a real enough width for its sign and decimal point or the field turns into asterisks. A format is code, and it deserves the same reading as the loop beside it.

Try these next

  • Write a header and two rows with A8,I6,F8.2 and confirm they line up.
  • Narrow an I5 field to I1 and describe what replaces the number.
  • Print the same real with F8.2, F0.2 and E10.3.

List-directed against formatted

Run it and compare the first line with the rest. Then change the width in the I5 line to I2, run again, and see what happens when the value no longer fits.

Output
Click "Run" to execute your code

A receipt that lines up

Run it and check that the total lands under the value column. Then add a fourth item — `bag`, 3 at 0.40 — and change the array sizes and the loop bound together.

Output
Click "Run" to execute your code

Build a table header and its rows from the same widths. If the header says A8,I8,F8.2 and the rows use A8,I8,F8.2, the columns line up by construction and keep lining up when the values grow.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does I0 mean in a format?