Format Specifications and Edit Descriptors

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-19. Every example was compiled with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) and run, and the examples were also built and run with LLVM Flang 23; the output shown is real output. Where the two compilers disagree, or one of them rejects the code, both results are shown. The rules are in Clause 13 of the standard (input/output editing): format specifications are 13.1 and 13.2, the form of a format item list 13.3, the interaction between the input/output list and the format (including reversion) 13.4, the data edit descriptors 13.7, the control edit descriptors 13.8, and character string edit descriptors 13.9. The format specifier of a data transfer statement is 12.6.2.2, and the changeable connection modes are 12.5.2 with their specifiers in 12.5.6 (open) and 12.6.2 (data transfer statements). List-directed and namelist formatting (13.10 and 13.11) have their own page, Internal Files, Namelist and Non-Advancing I/O.

A format tells a formatted read, write or print how to convert between the values in the input/output list and the characters of a record. It is a list of edit descriptors: data edit descriptors (i, f, es, a, …​) that each convert one list item, control edit descriptors (x, /, p, sp, …​) that move within the record or change a mode, and character string edit descriptors ('text') that write constant text. The format is separate from the I/O statement, so the same format can be reused, built at run time, or replaced by * to let the processor choose. The running example, heatflow, needs formats for three jobs: a readable report of the temperature field, the CSV rows written by write_csv (see Input/Output: Units and Files), and reading numbers back from text. The page follows the order in which a format is used: where it comes from, how format control walks through it, every edit descriptor, the connection modes that change how they behave, and then input, CSV and report output, diagnostics, and the compiler differences and deleted features that matter when old code or tutorials are reused.

The figure below shows the same values written by the descriptors most often compared: f8.3, es12.4, en12.3, g12.4 and, for integers, i8, i0, i8.5, g0 and z0. The cells are real output of both compilers.

Table of values written by the f8.3, es12.4, en12.3 and g12.4 edit descriptors for four real values, and by i8, i0, i8.5, g0 and z0 for two integers

Where a format comes from

A formatted data transfer statement names its format with the fmt= specifier or, in the short form, as its first positional item. The specifier (R1215, 12.6.2.2) is one of three things:

Form Meaning Clause

A character expression

it shall evaluate to a string that starts with a valid format specification; characters after the closing parenthesis are ignored. A character array is treated as if its elements were concatenated in array element order, but a character array element must contain a whole format by itself

12.6.2.2, 13.2.2

A statement label

the label of a format statement in the same scoping unit as the data transfer statement (C1232); a format statement must be labelled (C1301) and is not executable

12.6.2.2, 13.2.1

*

list-directed formatting: no explicit descriptors, the processor chooses the layout

13.10

A namelist group name in place of a format selects namelist formatting (13.11). The next example writes the same row of the temperature field with every explicit form, plus a format that is assembled at run time with an internal write, a format in a character array, and list-directed output:

program format_forms
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  integer, parameter :: nx = 5
  real(dp) :: row(nx) = [20.0_dp, 36.25_dp, 52.5_dp, 36.25_dp, 20.0_dp]
  character(len=*), parameter :: row_fmt = '(a, 5f8.2)'    ! a named constant is a format
  character(len=24) :: dyn_fmt
  character(len=16) :: parts(2)
  integer :: k

  ! 1. A character literal
  write (*, '(a, 5f8.2)') 'literal: ', row

  ! 2. A labelled FORMAT statement
  write (*, 100) 'label:   ', row
100 format (a, 5f8.2)

  ! 3. A character constant or variable
  write (*, row_fmt) 'named:   ', row

  ! 4. A format assembled at run time, here with the row length
  write (dyn_fmt, '("(a, ", i0, "f8.2)")') nx
  write (*, '(a)') 'built format: ' // trim(dyn_fmt)
  write (*, dyn_fmt) 'dynamic: ', row

  ! 5. A character array: the elements are concatenated in array element order
  parts = [character(len=16) :: '(a, ', '5f8.2)']
  write (*, parts) 'array:   ', row

  ! 6. List-directed: the processor chooses the layout
  write (*, *) 'star:', row(1:2)

  ! 7. A repeat count larger than the list is harmless: format control stops at the first
  !    data edit descriptor without an item
  write (*, '(a, 9f8.2)') 'extra:   ', row

  ! 8. Blanks are free and case does not matter
  write (*, '( A , 5 F 8 . 2 )') 'blanks:  ', row
  k = 0
  write (*, '(a, i0)') 'items printed with i0: ', k
end program format_forms

Output with gfortran:

literal:    20.00   36.25   52.50   36.25   20.00
label:      20.00   36.25   52.50   36.25   20.00
named:      20.00   36.25   52.50   36.25   20.00
built format: (a, 5f8.2)
dynamic:    20.00   36.25   52.50   36.25   20.00
array:      20.00   36.25   52.50   36.25   20.00
 star:   20.000000000000000        36.250000000000000
extra:      20.00   36.25   52.50   36.25   20.00
blanks:     20.00   36.25   52.50   36.25   20.00
items printed with i0: 0

Flang prints the same lines except the list-directed one, which is up to the processor:

 star: 20. 36.25

The points the example shows, and a few it does not:

  • A character expression is the general form. A parameter is the best way to share a constant format between statements; a character(len=*), parameter avoids a length that has to match. A format in a variable is read every time the statement executes, so an error in it is a run-time error (see When the format and the list disagree).

  • A labelled format statement has the label in columns 1 to 5 in fixed form and anywhere before the statement in free form. It is shared by every statement that names its label, which is its only advantage over a named character constant. It has the same syntax as the character form, but without the quotes. The deleted assigned-format feature (assign 10 to fmt) is covered under Deleted and nonstandard features.

  • Blanks are free and case does not matter. Blanks may appear anywhere in a format specification except inside a character string edit descriptor (13.2.1), and an edit descriptor is the same in upper and lower case, except inside a string (13.3.2). The example’s '( A , 5 F 8 . 2 )' is valid.

  • A format with a leading text item such as '("T = ", f6.2)' writes the text first. Quote the delimiters carefully: inside a format written with apostrophes, a string edit descriptor needs doubled apostrophes ('(''it''''s'', i3)'), so writing the format with quotation marks ("(a, 'it''s')" or the other way round) is the usual way out. Standard 13.2.2 (Note 2) shows the same rule with isn't.

  • A format can be built at run time, as the example builds (a, 5f8.2) from the row length with an internal write. The idiom is the way to apply a runtime width or number of columns; the *( ) form in the next section often removes the need for it.

  • print fmt, list is write (*, fmt) list, and print *, list is list-directed output to the standard output unit.

The format item list

The syntax of the list (R1303 to R1306, 13.3.1) is short:

Item Meaning

r edit-descriptor

the descriptor repeated r times; r is an unsigned integer literal without a kind parameter, positive (C1304, C1305)

r ( format-items )

a group repeated r times, nested to any depth

*( format-items )

the unlimited format item: the group repeats as often as there are items left (C1303: it shall contain a data edit descriptor, and it shall be the last item of the format)

item , item

items are separated by commas, which may be omitted only before or after a slash, before or after a colon, and between a p descriptor and a following real data edit descriptor (C1302)

A repeat count before / is the number of records to end, and a repeat count before a parenthesised group repeats the whole group (2(f6.1, i5)). The count applies to the group as a unit, and it is reused every time format control reverts to that group. The only descriptor that does not take a repeat count is a control descriptor other than /: 3x is the position descriptor x with n = 3, not three x descriptors. Nothing about a descriptor’s width or digits is shared between repeats: 3f8.2 is three independent f8.2, 2(f8.2, i3) is f8.2, i3, f8.2, i3.

The effective items of the I/O list (12.6.3) pair up with the data edit descriptors, left to right. An array or an array section contributes its elements in array element order, an implied do its generated items, and a derived-type object its components (unless it has a defined I/O procedure). A zero-sized array or an implied do with no iterations contributes no item; a zero-length character item does. A complex item needs two real descriptors and a derived-type item with a defined I/O procedure needs one dt. When a format needs an effective item and there is none, it stops.

Format control, reversion and the colon

Format control (13.4) interprets the format from left to right, pairing each data edit descriptor with the next effective item. The rules that decide what happens at the end of the format or when items run out are the ones programmers get wrong:

  • When format control meets a data edit descriptor and the list has no item left, it terminates. Whatever comes before that descriptor has been written; whatever follows has not. Control and string descriptors between the last used descriptor and the next unused data descriptor are processed.

  • A colon edit descriptor terminates format control if no effective item remains, and has no effect if one does (13.8.3). It is the way to stop a trailing string or separator from being written when the list ends.

  • When format control reaches the final right parenthesis and the list still has items, the record is ended as if by a slash, and the format reverts: it restarts at the beginning of the format item that is terminated by the last preceding right parenthesis (not counting one that belongs to a dt descriptor), or at the first left parenthesis if there is no such right parenthesis. If a repeat count precedes that parenthesis it is reused. The reused part shall contain at least one data edit descriptor.

  • The unlimited format item *( ) reverts to its own left parenthesis, without ending the record, as often as items remain. It writes a whole array into a single record, which is what a CSV row needs (13.4, Note 4).

flowchart TD A["Start: first item of the format"] --> B{"Next item in the format"} B -->|"data edit descriptor"| C{"Effective item left in the list?"} C -->|"yes"| D["Convert the item with this descriptor"] D --> B C -->|"no"| Z["Format control ends"] B -->|"control or string descriptor"| E["Position or write text, no list item used"] E --> B B -->|"colon"| F{"Effective item left in the list?"} F -->|"no"| Z F -->|"yes"| B B -->|"closing parenthesis of a star group"| G["Revert to its opening parenthesis, same record"] G --> B B -->|"closing parenthesis of the format"| H{"Effective item left in the list?"} H -->|"no"| Z H -->|"yes"| I["End the record, revert to the last preceding group or to the start"] I --> B

The example runs the cases on the temperature and identifier arrays of the field:

program reversion
  implicit none
  integer, parameter :: n = 7
  real :: temp(n) = [10.1, 3.0, 4.7, 1.0, 12.4, 5.0, 5.2]
  integer :: ids(n) = [101, 102, 103, 104, 105, 106, 107]

  ! The list is longer than the format: when the closing parenthesis is reached and items
  ! remain, format control starts a new record at the beginning of the format
  write (*, '(3f7.1)') temp

  ! With a leading text item the whole format is repeated, text included
  write (*, '(" T:", 3f7.1)') temp

  ! Reversion goes back to the last parenthesis group at the top level, so the text before
  ! the group is written once
  write (*, '("ids:", 3(1x, i4))') ids

  ! Pairs of items; reversion to a group reuses its repeat count
  write (*, '(1x, 2(f6.1, i5))') 10.1, 3, 4.7, 1, 12.4, 5, 5.2, 6

  ! The unlimited format item *( ) repeats without limit and without a new record
  write (*, '("temps =", *(f6.1, :, ","))') temp
  write (*, '(*(i0, :, ", "))') ids

  ! The colon stops format control when the list is exhausted
  write (*, '(a, i0, :, " of ", i0)') 'step ', 3, 10
  write (*, '(a, i0, :, " of ", i0)') 'step ', 3

  ! A text item after the last used data edit descriptor is still written without a colon
  write (*, '(a, i0, " done")') 'steps ', 3
end program reversion

The output is the same with gfortran and Flang:

   10.1    3.0    4.7
    1.0   12.4    5.0
    5.2
 T:   10.1    3.0    4.7
 T:    1.0   12.4    5.0
 T:    5.2
ids:  101  102  103
  104  105  106
  107
   10.1    3   4.7    1
  12.4    5   5.2    6
temps =  10.1,   3.0,   4.7,   1.0,  12.4,   5.0,   5.2
101, 102, 103, 104, 105, 106, 107
step 3 of 10
step 3
steps 3 done

Reading the output: the first statement has a 7-item list and a format with three descriptors, so it writes two full records and a third with one value. The second, with a leading text item, repeats the text on every record. The third has the text in front of a group, so reversion returns to the group (1x, i4) and the text ids: is written only once. The fourth shows a repeat count (2) on the group that is returned to, reused for the second record. The unlimited item writes the whole array in one record, and the colon removes the trailing comma and the trailing " of " text.

One reversion trap is worth spelling out. A format such as '(a, 3f7.1)' applied to a text label and seven reals does not write a text label and then seven numbers: after three numbers it reverts to the start of the format, so the fourth real goes to the a descriptor. Neither compiler diagnoses an a descriptor paired with a real (see When the format and the list disagree), so the output is garbage characters. When a list can be longer than the format, put the repeated part in a group and the text in front of it, or use the unlimited item.

When the format and the list disagree

A format is checked in two places. A format that is a character constant is checked by the compiler, which reports the position of a malformed descriptor. A format in a variable, a named constant passed through a dummy argument or a character array is checked when the statement runs. A list that does not match the descriptors is always a run-time matter.

Both compilers reject a malformed literal format at compile time. Source:

program bad_literal
  implicit none
  integer :: n = 41
  write (*, '(i4') n
  write (*, '(i0, 2.5f8.3)') n, 1.5
  write (*, '(i0, f8.)') n, 1.5
  write (*, '(i0, q)') n
end program bad_literal

gfortran 16.2 reports four errors:

04_bad_literal.f90:4:15:

    4 |   write (*, '(i4') n
      |               1
Error: Unexpected end of format string in format string at (1)
04_bad_literal.f90:5:18:

    5 |   write (*, '(i0, 2.5f8.3)') n, 1.5
      |                  1
Error: Unexpected element '.' in format string at (1)
04_bad_literal.f90:6:20:

    6 |   write (*, '(i0, f8.)') n, 1.5
      |                    1
Error: Nonnegative width required in format string at (1)
04_bad_literal.f90:7:17:

    7 |   write (*, '(i0, q)') n
      |                 1
Error: Unexpected element 'Q' in format string at (1)

Flang 23 reports four errors as well, with different wording:

error: Semantic errors in 04_bad_literal.f90
04_bad_literal.f90:4:17: error: Unterminated format expression
    write (*, '(i4') n
                  ^
04_bad_literal.f90:5:20: error: Unexpected '.' in format expression
    write (*, '(i0, 2.5f8.3)') n, 1.5
                     ^
04_bad_literal.f90:6:22: error: Expected 'F' edit descriptor 'd' value after '.'
    write (*, '(i0, f8.)') n, 1.5
                       ^
04_bad_literal.f90:7:19: error: Unexpected 'Q' in format expression
    write (*, '(i0, q)') n
                    ^

When the format comes from a variable, the same mistakes surface at run time, which the iostat= and iomsg= specifiers make easy to observe. The subroutine below writes the integer n and the real x into a character buffer with a format passed as an argument and reports the outcome:

program mismatch
  implicit none
  integer :: n = 41
  real :: x = 1.5

  call attempt('(f6.2)')              ! an integer item for an F descriptor
  call attempt('(i4, i4)')            ! a real item for an I descriptor
  call attempt('(i4, f6.2, i4)')      ! more descriptors than items: fine
  call attempt('(i4')                 ! missing right parenthesis
  call attempt('(i4, 2.5f8.3)')       ! malformed descriptor
  call attempt('(i4, f8.)')           ! missing d
  call attempt('("no edit")')         ! items but no data edit descriptor

contains

  subroutine attempt(fmt)
    character(len=*), intent(in) :: fmt
    character(len=60) :: buf
    character(len=200) :: msg
    integer :: ios, eol
    msg = ''
    write (buf, fmt, iostat=ios, iomsg=msg) n, x
    if (ios == 0) then
      print '(3a)', fmt, ' -> ok: ', trim(buf)
    else
      eol = index(msg, new_line('a'))                  ! gfortran appends the format and a marker line
      if (eol == 0) eol = len_trim(msg) + 1
      print '(2a, i0, 2a)', fmt, ' -> iostat=', ios, ' ', msg(1:eol-1)
    end if
  end subroutine attempt
end program mismatch

gfortran 16.2:

(f6.2) -> iostat=5006 Expected REAL for item 1 in formatted transfer, got INTEGER
(i4, i4) -> iostat=5006 Expected INTEGER or UNSIGNED for item 2 in formatted transfer, got REAL
(i4, f6.2, i4) -> ok:   41  1.50
(i4 -> iostat=5006 Unexpected element '4' in format
(i4, 2.5f8.3) -> iostat=5006 Unexpected element '.' in format
(i4, f8.) -> iostat=5006 Nonnegative width required in format
("no edit") -> iostat=5006 Insufficient data descriptors in format after reversion

Flang 23:

(f6.2) -> iostat=1005 Data edit descriptor 'F' may not be used with an INTEGER data item
(i4, i4) -> iostat=1005 Data edit descriptor 'I' may not be used with a REAL data item
(i4, f6.2, i4) -> ok:   41  1.50
(i4 -> iostat=1005 FORMAT missing at least one ')'
(i4, 2.5f8.3) -> iostat=1005 Invalid character in FORMAT; at offset 3 in format '(i4, 2.5f8.3)'
(i4, f8.) -> iostat=1005 Invalid FORMAT: integer expected at ')'
("no edit") -> iostat=1004 Internal write overran available records

The six messages differ in wording but agree on the cause. The iostat values are processor dependent (5006 with gfortran, 1004 and 1005 with Flang) and only their sign matters: a positive value is an error (12.11.5). Note how ("no edit") with two items fails: the list has items but the format has no data edit descriptor, which 13.4 forbids; gfortran detects it when it tries to revert, Flang when its internal write runs out of record.

Without iostat= the same error ends the program. gfortran 16.2:

program runtime_error
  implicit none
  integer :: step = 200
  print '(a, f8.2)', 'step = ', step
  print '(a)', 'not reached'
end program runtime_error
At line 4 of file 24_runtime_error.f90 (unit = 6, file = 'stdout')
Fortran runtime error: Expected REAL for item 2 in formatted transfer, got INTEGER
(a, f8.2)
    ^

Flang 23 prints fatal Fortran runtime error with the source line and Data edit descriptor 'F' may not be used with an INTEGER data item, and aborts. The same holds for input: reading a record that has a character where a digit is required ('12x45' with i8) gives gfortran Bad value during integer read and Flang Bad character 'x' in INTEGER input field.

A mismatch that is not reported is the use of the a descriptor with a non-character item. The standard requires a character item for a (13.7.4), and both compilers accept an integer or real item silently, even with -std=f2023, and write or read the item’s bytes. It is a legacy extension that turns a type error into garbage output:

program a_with_integer
  implicit none
  integer :: ios, k
  character(len=100) :: msg
  character(len=8) :: buf
  msg = ''
  write (buf, '(a)', iostat=ios, iomsg=msg) 1818846825
  print '(a, i0, a, a)', 'iostat=', ios, ' iomsg=', trim(msg)
  print '(a, 4z3.2)', 'bytes: ', (ichar(buf(k:k)), k = 1, 4)
end program a_with_integer

Both compilers print the same:

iostat=0 iomsg=
bytes:  69 66 69 6C

The integer 1818846825 is 0x6C696669; the four bytes written are 69 66 69 6C hexadecimal, that is, the text ifil. Do not rely on it; on a big-endian processor the bytes would come out in the other order. The portable ways to print text are an a item that is a character, and transfer for conversions that are really needed.

Integer editing: i, b, o and z

iw writes an integer right-justified in a field of w characters, iw.m writes at least m digits (padding with leading zeros), and i0 writes the narrowest field (13.7.2.2). The b, o and z descriptors write the bit pattern of an integer (or, as the standard allows, a real or complex value) in binary, octal and hexadecimal, without leading zero bits (13.7.2.4). Rules worth knowing:

  • If the number does not fit in w characters the field is filled with asterisks (13.7.2.1 rule 5). A leading minus sign counts. A field of width zero is never too narrow.

  • iw.0 with the value zero writes only blanks, so i6.0 is a way to suppress zeros in a table. i0.0 of zero writes a single blank.

  • On input m has no effect, and w shall not be zero.

  • For b, o and z the value is the internal bit pattern: a negative integer shows its two’s complement representation, with as many digits as the kind has bits (z0 of -7 for a 32-bit integer is FFFFFFF9), and a 1-byte integer of 100 is 64. They also read: a z8 field supplies the bit pattern of a 32-bit integer.

  • For an enumeration type or an enum type, i, b, o and z write the ordinal or the integer value; g is allowed for enum types (13.7.2.1, 13.7.2.2). Fortran 2023 added the enumeration type, which neither compiler implements; the page Enumerations discusses the I/O rules.

program integer_boz
  use, intrinsic :: iso_fortran_env, only: int8, int64, real64
  implicit none
  integer :: n = 41, neg = -7, zero = 0
  integer(int8) :: b = 100_int8
  integer(int64) :: big = 123456789012_int64
  real(real64) :: x = 1.0_real64

  print '(a, i0, "|", i6, "|", i6.4, "|", i2, "|", i3.3, "|")', 'i:  ', n, n, n, n, n
  print '(a, i0, "|", i6, "|", i6.4, "|", i2, "|", i3.3, "|")', 'neg:', neg, neg, neg, neg, neg
  print '(a, i0, "|", i6.0, "|", i0.0, "|", i3.0, "|")', 'zero:', zero, zero, zero, zero
  print '(a, i0, "|", i4, "|", i15, "|")', 'int64: ', big, b, big
  print '(a, "[", sp, i0, ss, "]", " [", sp, i0, "]")', 'sign: ', n, neg

  ! b, o and z write the bit pattern without leading zero bits
  print '(a, b0, "|", b12.12, "|", o0, "|", z0, "|", z8.8, "|")', 'boz: ', n, n, n, n, n
  print '(a, b0, "|", z0, "|", z0, "|")', 'neg: ', neg, neg, b
  ! b, o and z also accept real data: they show the bit pattern of the value
  print '(a, z0, "|", z16.16, "|", b0)', 'real 1.0: ', x, x, 1.0
  print '(a, 2z4.4)', 'two: ', 255, 4096
end program integer_boz

The output is the same with gfortran and Flang:

i:  41|    41|  0041|41|041|
neg:-7|    -7| -0007|-7|***|
zero:0|      | |   |
int64: 123456789012| 100|   123456789012|
sign: [+41] [-7]
boz: 101001|000000101001|51|29|00000029|
neg: 11111111111111111111111111111001|FFFFFFF9|64|
real 1.0: 3FF0000000000000|3FF0000000000000|111111100000000000000000000000
two: 00FF1000

The first line shows i3.3 writing 041 for 41, and the second shows the same descriptor for -7 needing four characters, so it is filled with asterisks. The third line shows the zero special cases. The sign: line uses the sp and ss control descriptors, which are covered with the other control descriptors.

Real editing: f, e, en, es, ex and d

All the real descriptors take w.d: the field width and the number of digits after the decimal point (or, for e, en and es, the digits of the significand). Fortran allows w = 0 for output (13.7.2.1 rule 6), in which case the processor chooses the narrowest field that does not overflow; f0.3 is the usual way to write a number without padding.

Descriptor Output form Notes

fw.d

fixed point, d digits after the point

the optional zero before the decimal point is dropped if the field is too narrow to hold it (f4.2 of 0.5 is 0.50, f3.2 is .50); a value that does not fit gives asterisks. Negative values that round to zero keep their minus sign (-0.000)

ew.d, ew.dEe

0.xxxxE+ee: significand in [0.1, 1), d digits

the exponent has two digits when it fits, three digits drop the letter, and ew.dEe gives exactly e digits and the letter (the field is filled with asterisks if the exponent needs more)

dw.d

as e, with D for the exponent letter

a processor may write E instead of D; no Ee form (13.7.2.3.3). Obsolescent in the Fortran 2028 draft (J3/26-007r1 13.3.2)

enw.d, enw.dEe

engineering notation: significand in [1, 1000), exponent a multiple of three

the scale factor has no effect (13.7.2.3.4)

esw.d, esw.dEe

scientific notation: one digit before the point

the scale factor has no effect (13.7.2.3.5)

exw.d, exw.dEe

hexadecimal significand 0X1.8P+0, exact when d is zero

the binary exponent is a decimal integer after P (13.7.2.3.6)

The rule of thumb behind the figure: f for values in a known range, es for anything that another program has to read back, and g0 or f0.d only for output people read. Real output for five values with the common descriptors, plus the d and g0 forms:

program real_forms
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: v(5) = [0.0_dp, 3.14159265358979_dp, -0.000123456_dp, 4721.3_dp, 6.02214076e23_dp]
  integer :: i

  print '(a)', 'value             f10.3      e12.4        es12.4       en12.3       d12.4        g0'
  do i = 1, size(v)
    print '(g17.8, 1x, f10.3, 1x, e12.4, 1x, es12.4, 1x, en12.3, 1x, d12.4, 1x, g0)', &
      v(i), v(i), v(i), v(i), v(i), v(i), v(i)
  end do

  ! exponent width and forms
  print '(a, e12.4e1, "|", e12.4e2, "|", e12.4e4, "|", e12.4e0, "|", es12.4e3, "|")', 'e: ', &
    4721.3_dp, 4721.3_dp, 4721.3_dp, 4721.3_dp, 4721.3_dp
  print '(a, e12.4e1, "|", e12.4e2, "|", e12.4e4, "|", e12.4e0, "|", es12.4e3, "|")', 'e: ', &
    1.0e-123_dp, 1.0e-123_dp, 1.0e-123_dp, 1.0e-123_dp, 1.0e-123_dp
  ! f with w = 0, e with w = 0
  print '(a, f0.3, "|", f0.0, "|", e0.3, "|", es0.3, "|", en0.3, "|")', 'w=0: ', v(2), v(2), v(2), v(2), v(2)
  print '(a, f0.3, "|", f0.3, "|", f0.3, "|")', 'zero: ', 0.0_dp, 0.5_dp, -0.25_dp
end program real_forms

Output with gfortran:

value             f10.3      e12.4        es12.4       en12.3       d12.4        g0
    0.0000000          0.000   0.0000E+00   0.0000E+00    0.000E+00   0.0000D+00 0.0000000000000000
    3.1415927          3.142   0.3142E+01   3.1416E+00    3.142E+00   0.3142D+01 3.1415926535897900
  -0.12345600E-03     -0.000  -0.1235E-03  -1.2346E-04 -123.456E-06  -0.1235D-03 -0.12345600000000001E-003
    4721.3000       4721.300   0.4721E+04   4.7213E+03    4.721E+03   0.4721D+04 4721.3000000000002
   0.60221408E+24 **********   0.6022E+24   6.0221E+23  602.214E+21   0.6022D+24 0.60221407599999999E+024
e:    0.4721E+4|  0.4721E+04|0.4721E+0004|   0.4721E+4| 4.7213E+003|
e: ************|************|0.1000E-0122| 0.1000E-122| 1.0000E-123|
w=0: 3.142|3.|0.314E+001|3.142E+000|3.142E+000|
zero: .000|.500|-.250|

The g0 column is the one place where Flang writes something else, because the standard leaves the digits of g0 to the processor (13.7.5.2.3: "reasonable processor-dependent values"). Flang’s g0 and e0.3/es0.3/en0.3 forms of the same program:

Value gfortran 16.2 g0 Flang 23 g0

0.0

0.0000000000000000

0.

3.14159265358979

3.1415926535897900

3.14159265358979

-0.000123456

-0.12345600000000001E-003

-.123456E-3

4721.3

4721.3000000000002

4721.3

6.02214076e23

0.60221407599999999E+024

.602214076E+24

gfortran writes the 17 significant digits of the stored real64, Flang the shortest digits that read back the same value, so gfortran shows binary rounding noise (4721.3000000000002) that Flang hides. Both are standard-conforming, and neither is what a person would call "4721.3" in all cases. The same program also shows the w = 0 exponent forms: e0.3 of 3.14159 is 0.314E+001 with gfortran and .314E+01 with Flang, and es0.3 is 3.142E+000 against 3.142E+00. Table 13.1 of the standard (E0.d has "the minimum number of digits required") suggests E+1 and E+0, which neither compiler writes, so keep an explicit w when the exact text matters.

Exponent forms and widths

The exponent field is the part of e, d, en and es that surprises people (Tables 13.1 to 13.3). With w > 0 and no Ee suffix, the exponent is E+dd or E-dd when its absolute value is at most 99, and +ddd or -ddd (no letter) when it needs three digits. With an Ee suffix the exponent has exactly e digits and the letter, and the whole field is filled with asterisks if the exponent needs more. Ee with e = 0 and w = 0 give the shortest exponent. The example prints the same values with several exponent widths:

program exponent_forms
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: big = 1.0e123_dp, small = 1.0e-123_dp, mid = 12345.678_dp

  ! Three-digit exponents lose the letter in the plain Ew.d forms (Table 13.1)
  print '(a, "[", e12.4, "][", e12.4, "][", d12.4, "][", e12.4, "]")', 'e/d: ', big, small, big, mid
  print '(a, "[", es12.4, "][", es12.4, "][", en12.4, "][", en12.4, "]")', 'es/en: ', big, small, big, small
end program exponent_forms

The output is the same with gfortran and Flang:

e/d: [  0.1000+124][  0.1000-122][  0.1000+124][  0.1235E+05]
es/en: [  1.0000+123][  1.0000-123][  1.0000+123][  1.0000-123]

Here is the output of the exponent-width lines of the real-forms program above, for 4721.3 and 1.0e-123:

e:    0.4721E+4|  0.4721E+04|0.4721E+0004|   0.4721E+4| 4.7213E+003|
e: ************|************|0.1000E-0122| 0.1000E-122| 1.0000E-123|

The columns are e12.4e1, e12.4e2, e12.4e4, e12.4e0 and es12.4e3. With e1 or e2 the exponent -123 does not fit, so the field is asterisks.

The EX descriptor

ex writes a real as a hexadecimal significand and a binary exponent, 0X1.6P+0 for 1.375 with ex0.1 (the standard’s own example). The format is exact when d is zero, which makes ex0.0 the way to write a real so that it reads back to the identical bit pattern in a text file. gfortran 16.2 does not implement it. Flang 23 does:

program ex_descriptor
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  ! Hexadecimal-significand output with the EX descriptor (13.7.2.3.6).
  ! The exact digits of the first hexadecimal digit are processor dependent.
  print '(a, ex0.0, "|", ex20.4, "|", ex0.2)', 'ex: ', 1.375_dp, 1.375_dp, 15.625_dp
  print '(a, ex0.0)', 'tiny: ', 0.1_dp
end program ex_descriptor

gfortran 16.2 reads ex as e followed by an unknown item and rejects the program at compile time:

07_ex_descriptor.f90:7:21:

    7 |   print '(a, ex0.0, "|", ex20.4, "|", ex0.2)', 'ex: ', 1.375_dp, 1.375_dp, 15.625_dp
      |                     1
Error: Period required in format specifier E at (1)
07_ex_descriptor.f90:8:21:

    8 |   print '(a, ex0.0)', 'tiny: ', 0.1_dp
      |                     1
Error: Period required in format specifier E at (1)

Flang 23 output:

ex: 0XB.P-3|         0XB.0000P-3|0XF.A0P+0
tiny: 0XC.CCCCCCCCCCCDP-7

The binary exponent is processor dependent (13.7.2.3.6): Flang writes 0XB.P-3 for 1.375, where the standard’s example 0X1.6P+0 is another valid way to write the same number with a different choice of leading digit. Reading hexadecimal significands is part of f editing on input (13.7.2.3.2); see Formatted input.

The D descriptor

dw.d is ew.d with the letter D in the exponent. It dates from the time when a program had real and double precision and nothing else, and its documentation in the standard says it may emit E. J3/26-007r1, the Fortran 2028 draft, specifies the Dw.d descriptor as obsolescent (13.3.2 and 13.7.2.3.3, with the rationale in B.3.14: it cannot name an exponent width, and e works for every real kind). In F2023 it is a regular descriptor. New code should use e or es.

The G and G0 descriptors

gw.d is the generalized descriptor: it edits an integer like iw, a logical like lw, a character like aw, and a real by choosing between f and e forms from the magnitude (13.7.5). For a real x converted to d significant digits, with 10^(s-1) <= |x| < 10^s: if 0 <= s <= d the value is written with f(w-n).(d-s) followed by n blanks (n is 4, or e+2 with an Ee suffix); otherwise e editing (with the scale factor applied) is used. The blanks are where the exponent would be, which keeps the columns of a table aligned.

g0 (width zero) removes the padding and chooses the digits itself: it is the descriptor for building text from mixed values without padding. A d or e makes sense for reals only; for integer, logical and character items they have no effect. gw without .d is an error for w > 0 (C1307), so g6 is invalid and g6.0 is the shortest valid form.

program generalized
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: v(6) = [0.05_dp, 0.5_dp, 12.5_dp, 12345.0_dp, 1.0e7_dp, 1.0e-3_dp]
  integer :: i

  ! g13.6: F when 0.1 <= |x| < 10**6 (to d significant digits), otherwise E
  print '(a)', '            g13.6              g13.6e2              g0.6'
  do i = 1, size(v)
    print '(g13.6, " |", g13.6e2, " |", g0.6)', v(i), v(i), v(i)
  end do

  ! generalized editing of the other intrinsic types
  print '(a, g0, "|", g5.2, "|", g0, "|", g3.0, "|", g0, "|", g6.0, "|")', 'types: ', 42, 42, .true., .false., &
    'ftcs', 'ftcs'
  print '(a, 2(g0.4, 1x), "|", 2g12.4)', 'complex: ', cmplx(1.5_dp, -2.25_dp, dp), cmplx(1.5_dp, -2.25_dp, dp)
end program generalized

gfortran 16.2:

            g13.6              g13.6e2              g0.6
 0.500000E-01 | 0.500000E-01 |0.500000E-001
 0.500000     | 0.500000     |0.500000
  12.5000     |  12.5000     |12.5000
  12345.0     |  12345.0     |12345.0
 0.100000E+08 | 0.100000E+08 |0.100000E+008
 0.100000E-02 | 0.100000E-02 |0.100000E-002
types: 42|   42|T|  F|ftcs|  ftcs|
complex: 1.500 -2.250 |   1.500      -2.250

Flang 23 writes the same lines except the g0.6 column, where it prints .500000E-1, .500000, .100000E+8 and .100000E-2 for the first, second, fifth and sixth values (the g13.6 columns and the other rows are identical, and so is the complex line).

Two things to note. First, g13.6 of 0.5 writes 0.500000 followed by four blanks (the n of the rule); the blank padding is part of the field. Second, g0.6 of 0.05 and of 1e7 goes to the exponent form with its own exponent length, and the exponent differs between the compilers: E-001 and E+008 with gfortran, E-1 and E+8 with Flang. The standard only requires "reasonable processor-dependent values", so g0 is for people and not for files that must be diffed. Earlier editions of the standard gave a different result for some values and rounding modes (and, for gw.0, Fortran 95 and Fortran 90 differ from the current rule); 4.3.5 to 4.3.7 of the standard list these among its compatibility notes, so a table written for an old compiler can differ in a few g cells.

Logical, character and complex editing

lw writes w-1 blanks and T or F; on input it takes optional blanks, an optional period, then T or F, and ignores the rest of the field, so .true., .t., T and tx are all true (13.7.3). aw writes a character item in w characters, truncating on the right when w is smaller than the length and padding on the left when it is larger; without w it uses the length of the item. On input, a field wider than the variable keeps the rightmost characters, and a narrower field pads the variable with blanks on the right (13.7.4). A complex item is two real descriptors, which can differ; control and string descriptors between them are allowed, so (f8.1, '+', f6.2, 'i') writes the complex number (1.5, -2.25) as 1.5+ -2.25i, each part right-justified in its field.

program logical_char_complex
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  character(len=8) :: name = 'ftcs'
  complex(dp) :: z = cmplx(1.5_dp, -2.25_dp, dp)
  logical :: ok = .true., bad = .false.

  ! logical: l w writes w-1 blanks then T or F
  print '(a, "[", l1, "][", l4, "][", l1, "][", l3, "]")', 'logical: ', ok, ok, bad, bad
  ! a: the field width defaults to the length; a w shorter than the length keeps the left part, a longer one
  ! pads on the left
  print '(a, "[", a, "][", a4, "][", a12, "][", a, "]")', 'a: ', name, name, name, trim(name)
  ! a string edit descriptor can be written with either delimiter, doubled to represent itself
  print '(a, "He said ""hi""", 1x, ''it''''s'', " 2", 1x, "it''s")', 'lit: '
  print "(a, 'say ""x"" and ''y''')", 'lit2: '
  ! complex: two real descriptors, which may differ; other descriptors may appear between them
  print '(a, 2f8.3)', 'z: ', z
  print '(a, f8.1, "+", f6.2, "i")', 'z: ', z
  print '(a, "(", es10.2, ",", es10.2, ")", a)', 'z: ', z, ' (es)'
  print '(a, 4f8.3)', 'two z: ', z, conjg(z)
end program logical_char_complex

The output is the same with gfortran and Flang:

logical: [T][   T][F][  F]
a: [ftcs    ][ftcs][    ftcs    ][ftcs]
lit: He said "hi" it's 2 it's
lit2: say "x" and 'y'
z:    1.500  -2.250
z:      1.5+ -2.25i
z: (  1.50E+00, -2.25E+00) (es)
two z:    1.500  -2.250   1.500   2.250

The AT descriptor (Fortran 2023)

The a descriptor writes a fixed-length string with its trailing blanks, which is why programs are full of trim. Fortran 2023 added at, which writes the value without trailing blanks and writes nothing at all for a blank string. It is output-only (13.7.4), and it is the one that makes (at, '=', i0) a complete format for a name = value line. Flang 23 accepts it; gfortran 16.2 does not recognise it. Source:

! Fortran 2023 AT edit descriptor (clause 13.7.4). Accepted by LLVM Flang 23.
! GNU Fortran 16.2 rejects it (see the page text for the real diagnostic).
program at_edit
  implicit none
  character(len=12) :: solver = 'ftcs'
  character(len=12) :: blank = ' '
  integer :: n = 41

  write (*, '("[", a, "]")') solver              ! a: the whole field, 12 characters
  write (*, '("[", at, "]")') solver             ! at: trailing blanks removed
  write (*, '("[", at, "]")') blank              ! all blanks: no output at all
  write (*, '(at, "=", i0)') solver, n           ! ftcs=41, no trim() needed
end program at_edit

Flang 23 output:

[ftcs        ]
[ftcs]
[]
ftcs=41

gfortran 16.2 reads at as a followed by a t position descriptor with no number and reports, for each use:

23_at_edit.f90:10:19:

   10 |   write (*, '("[", at, "]")') solver             ! at: trailing blanks removed
      |                   1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
23_at_edit.f90:11:19:

   11 |   write (*, '("[", at, "]")') blank              ! all blanks: no output at all
      |                   1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
23_at_edit.f90:12:14:

   12 |   write (*, '(at, "=", i0)') solver, n           ! ftcs=41, no trim() needed
      |              1
Error: Legacy Extension: Missing comma in FORMAT string at (1)

Both facts are consistent with the Flang documentation, which lists at as not yet implemented while the compiler accepts it. The portable spelling for now is a with trim. Source: J3/23-007r1 13.7.4

The DT descriptor

dt (13.7.6) hands an item of derived type to a user-defined I/O procedure; the character literal after dt and the integer list in parentheses are passed to the procedure as iotype (with DT prefixed) and v_list. The binding, the interface of the procedure and its use by the grid_t of heatflow are described in Derived-Type and Asynchronous I/O. A derived-type item that has no accessible defined I/O procedure is written component by component, each with its own descriptor.

Overflow, infinity and NaN

A field that is too narrow for a number is filled with asterisks: the processor never truncates the digits (13.7.2.1 rule 5). The standard lets the processor drop optional characters (the zero before the point, a plus sign) before it gives up, so f4.2 of 0.5 fits and f3.2 fits only without the zero. For an IEEE infinity the output is Infinity or Inf, right-justified, depending on w (at least 8, or 3 without a sign, and one more with a sign), and NaN for a NaN, optionally followed by processor-dependent characters in parentheses; a field too narrow for any form is asterisks (13.7.2.3.2). f0.1 and g0 give NaN. A negative zero or a negative value that rounds to zero keeps its minus sign: -0.0004 with f8.3 is -0.000.

program overflow_special
  use, intrinsic :: iso_fortran_env, only: real64
  use, intrinsic :: ieee_arithmetic, only: ieee_value, ieee_quiet_nan, ieee_positive_inf, ieee_negative_inf
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: big = 123456.789_dp
  real(dp) :: pinf, ninf, nan

  pinf = ieee_value(1.0_dp, ieee_positive_inf)
  ninf = ieee_value(1.0_dp, ieee_negative_inf)
  nan = ieee_value(1.0_dp, ieee_quiet_nan)

  ! field too narrow: asterisks, never a truncated number
  print '(a, "[", f6.2, "][", f9.2, "][", i3, "][", e8.3, "][", es9.3, "]")', 'overflow: ', big, big, 1234, big, big
  print '(a, "[", f0.2, "][", i0, "]")', 'w=0: ', big, 1234
  ! just enough room: the optional leading zero is dropped
  print '(a, "[", f4.2, "][", f3.2, "][", f4.2, "]")', 'fit: ', 0.5_dp, 0.5_dp, -0.5_dp
  ! infinity and NaN, right justified, in a field of width w
  print '(a, "[", f8.3, "][", f3.1, "][", f4.1, "][", f2.1, "][", f9.3, "]")', 'inf: ', pinf, pinf, ninf, pinf, ninf
  print '(a, "[", f8.3, "][", f3.1, "][", f2.1, "][", g0, "][", f0.1, "]")', 'nan: ', nan, nan, nan, nan, nan
  print '(a, "[", sp, f8.3, "]", ss, "[", e12.3, "]")', 'sign: ', pinf, pinf
  ! negative zero keeps its sign in formatted output of a real that rounds to zero
  print '(a, f8.3, 1x, f8.3, 1x, f8.3)', 'neg0: ', -0.0004_dp, -0.0_dp, 0.0004_dp
end program overflow_special

The output is the same with gfortran and Flang:

overflow: [******][123456.79][***][.123E+06][1.235E+05]
w=0: [123456.79][1234]
fit: [0.50][.50][-.50]
inf: [Infinity][Inf][-Inf][**][-Infinity]
nan: [     NaN][NaN][**][NaN][NaN]
sign: [    +Inf][    Infinity]
neg0:   -0.000   -0.000    0.000

The sign: line shows two things about infinity: sp forces +Inf, and e12.3 is wide enough for Infinity, so it writes the long form. The neg0: line shows that the minus sign on -0.0 is kept. Code that reads these values must accept them: the f descriptor reads Inf, Infinity and NaN (with optional parentheses), as 13.7.2.3.2 says and Formatted input shows. The IEEE arithmetic module that creates and tests them is covered in Floating-Point and IEEE Arithmetic.

Control edit descriptors

Control descriptors do not consume a list item. They position within the record, end records, set modes, or stop format control.

Descriptor Effect Clause

tn, tln, trn, nx

move to column n (relative to the left tab limit, normally the start of the record), n columns left, n columns right (nx is the same as trn). A move does not write blanks by itself: skipped positions that nothing overwrites are blanks, and a position that is only skipped at the end of the record does not lengthen it

13.8.1

/ and r/

end the current record; on output, an empty record follows, and r/ ends r records. On input, the rest of the record is skipped

13.8.2

:

terminate format control if no list items are left

13.8.3

ss, sp, s

sign mode: no optional plus, always plus, processor choice

13.8.4

lzs, lzp, lz

leading zero mode (Fortran 2023): suppress, print, processor choice

13.8.5

kp

scale factor, k a signed integer: output with f, e, d and g, and input with every real descriptor when the field has no exponent

13.8.6

bn, bz

blank interpretation on input: ignore, or read as zeros

13.8.7

ru, rd, rz, rn, rc, rp

rounding mode: up, down, toward zero, nearest, compatible (away from zero on ties), processor default

13.8.8

dc, dp

decimal comma, decimal point

13.8.9

Position, tab and slash

program position_slash
  implicit none
  character(len=20) :: line
  integer :: a, b, c

  ! x and tr skip forward, tl goes back, t is absolute (relative to the left tab limit, the record start here)
  print '(a, 3x, a, tr2, a)', 'x', 'y', 'z'
  print '(t11, a, t1, a)', 'right', 'left'
  print '(a, t3, a, tl2, a)', 'abcdef', 'XY', 'ZZ'
  ! skipped positions that are never written are blanks; nothing is written by a position descriptor itself
  print '(a, 10x)', 'trailing x at the end writes nothing: ['
  print '(a, 4x, a)', '[', ']'

  ! a slash ends a record; r/ ends r records, so r-1 empty records appear between
  print '(a, /, a, 2/, a)', 'line 1', 'line 2', 'line 4 after one blank'
  print '(/, a)', 'leading slash gives an empty first line'

  ! on input the same descriptors skip and re-read: here the same columns are read twice
  line = '  12  34  56'
  read (line, '(2x, i2, 2x, i2, 2x, i2)') a, b, c
  print '(a, 3i4)', 'skip with x: ', a, b, c
  read (line, '(t3, i2, t7, i2, t3, i4)') a, b, c
  print '(a, 3i6)', 'with t:      ', a, b, c

  read (line, '(tr2, i2, tl2, i2)') a, b
  print '(a, 2i6)', 'tr and tl:   ', a, b
end program position_slash

The output is the same with gfortran and Flang:

x   y  z
left      right
abZZef
trailing x at the end writes nothing: [
[    ]
line 1
line 2

line 4 after one blank

leading slash gives an empty first line
skip with x:   12  34  56
with t:          12    34    12
tr and tl:       12    12

t11 before right moves to column 11, the following t1 returns to column 1, and the later text overwrites the blanks: left right. tl2 after XY goes back two columns to overwrite it. 10x at the end of a record writes no characters, because a position descriptor does not by itself lengthen a record (13.8.1.1), but a later item would be preceded by the skipped blanks. A slash ends the record; with the text line 2 followed by 2/ and line 4, a blank line sits between. On input the same descriptors let a program read the same columns twice, as the last lines do.

Sign, scale factor, rounding and decimal comma

The sign, scale, rounding and decimal descriptors change a mode for the rest of the statement (the next section explains how long the effect lasts). The example uses all of them; the p lines are the ones that cause confusion:

program sign_scale_round_decimal
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: t = 36.25_dp, u = 0.125_dp, w = 2.5_dp, small = 0.000123456_dp
  real(dp) :: x
  character(len=12) :: text

  ! sign control: ss never writes the optional +, sp always does, s returns to the processor default
  print '(a, ss, f7.2, sp, f7.2, s, f7.2)', 'ss sp s: ', t, t, t

  ! scale factor k: f multiplies by 10**k; e keeps the digits and shifts the exponent; es and en ignore it
  print '(a, 0pf9.3, 1pf9.3, 2pf9.3, -1pf9.3)', 'f:  ', t, t, t, t
  print '(a, 0pe12.4, 1pe12.4, 2pe12.4, -1pe12.4)', 'e:  ', t, t, t, t
  print '(a, 2pes12.4, 2pen12.4)', 'es: ', t, t
  ! p stays in effect until changed, within one statement
  print '(a, 1pe10.3, f8.2, 0p, f8.2)', 'p:  ', t, t, t
  ! on input the scale factor divides, but only when the field has no exponent
  text = '  3625'
  read (text, '(f6.2)') x
  print '(a, f10.4)', 'read 0p f6.2:  ', x
  read (text, '(2pf6.2)') x
  print '(a, f10.4)', 'read 2p f6.2:  ', x
  read (text, '(-2pf6.0)') x
  print '(a, f10.2)', 'read -2p f6.0: ', x
  text = '  3.625'
  read (text, '(2pf7.2)') x
  print '(a, f10.5)', 'read 2p, with a decimal point in the field: ', x

  ! rounding of the last digit: nearest (processor chosen ties), up, down, zero, compatible (away on ties)
  print '(a, rn, f6.2, ru, f6.2, rd, f6.2, rz, f6.2, rc, f6.2, rp, f6.2)', 'u=0.125: ', u, u, u, u, u, u
  print '(a, rn, f6.0, ru, f6.0, rd, f6.0, rz, f6.0, rc, f6.0)', 'w=2.5:   ', w, w, w, w, w
  print '(a, rn, f6.2, ru, f6.2, rd, f6.2, rz, f6.2, rc, f6.2)', 'u=-0.125:', -u, -u, -u, -u, -u

  ! decimal comma: dc affects f, e, en, es, ex, d and g in both directions
  print '(a, dc, f8.3, dp, f8.3)', 'dc dp: ', t, t
  text = '  36,25'
  read (text, '(dc, f7.2)') x
  print '(a, dp, f8.3)', 'read dc: ', x
  print '(a, dc, g0.4, 1x, es12.3)', 'dc g0, es: ', small, small
end program sign_scale_round_decimal

The output is the same with gfortran and Flang except for the last line, where gfortran gives 0,1235E-003 and Flang ,1235E-3 for g0.4 (the digits of g0 are processor dependent). gfortran 16.2:

ss sp s:   36.25 +36.25  36.25
f:     36.250  362.500 3625.000    3.625
e:    0.3625E+02  3.6250E+01  36.250E+00  0.0362E+03
es:   3.6250E+01 36.2500E+00
p:   3.625E+01  362.50   36.25
read 0p f6.2:     36.2500
read 2p f6.2:      0.3625
read -2p f6.0:  362500.00
read 2p, with a decimal point in the field:    0.03625
u=0.125:   0.12  0.13  0.12  0.12  0.13  0.12
w=2.5:       2.    3.    2.    2.    3.
u=-0.125: -0.12 -0.12 -0.13 -0.12 -0.13
dc dp:   36,250  36.250
read dc:   36.250
dc g0, es: 0,1235E-003    1,235E-04

The behaviours the standard defines, and the example shows:

  • kp scale factor. On output with f, the number written is the internal value multiplied by 10k (1pf9.3 of 36.25 is 362.500); with e and d, the digits are shifted k places and the exponent reduced by k (1pe12.4 is 3.6250E+01, with one digit before the point); with g the effect is suspended unless e editing is selected; with en, es and ex there is no effect (13.8.6). The scale factor, once set, remains for the rest of the statement, so a format that needs 1p for one item must go back with 0p before items that should not scale, as the p: line shows. The scale factor is not a mode that an open can set; it starts at 0 for each connection.

  • kp on input. The external number equals the internal value times 10k, but only when the field has no exponent: 2p turns the field 3625 read with f6.2 into 0.3625, -2p turns the field 3625 read with f6.0 into 362500, and a field that carries an exponent is not scaled. Note the effect of an explicit decimal point: 3.625 with 2pf7.2 is also scaled, to 0.03625.

  • Rounding. The default is processor dependent (12.5.6.17 Note). rn is the IEEE round to nearest, ties to even (0.125 with f6.2 is 0.12), rc is away from zero on ties (0.13), ru and rd go up and down (for the negative value -0.125, ru gives -0.12 and rd -0.13), and rz truncates. The rounding mode also applies to input conversions.

  • Decimal comma. dc writes 36,250 and reads 36,25, and it also affects the separator that list-directed output uses (a semicolon, 13.6). It applies to f, e, en, es, ex, d and g, both directions.

  • sp, ss, s. They apply to i, f, e, en, es, ex, d and g on output only.

Leading zeros (Fortran 2023)

Whether a value below one is written as 0.500 or .500 is the leading zero mode, a Fortran 2023 addition. The descriptors are lzp (print the zero), lzs (suppress it) and lz (processor choice), they affect f, e, d and g output only (13.8.5), and Flang 23 accepts them. gfortran 16.2 does not recognise the new descriptors:

! Fortran 2023: lzs, lzp and lz control edit descriptors (13.8.5).
program leading_zero
  implicit none
  real :: x(3) = [0.5, -0.25, 12.5]
  print '(a, lzp, 3f8.3)', 'lzp: ', x
  print '(a, lzs, 3f8.3)', 'lzs: ', x
  print '(a, lz, 3f8.3)', 'lz:  ', x
  print '(a, lzs, 3es10.2, 3g10.3)', 'lzs: ', x, x
end program leading_zero

Flang 23 output:

lzp:    0.500  -0.250  12.500
lzs:     .500   -.250  12.500
lz:     0.500  -0.250  12.500
lzs:   5.00E-01 -2.50E-01  1.25E+01  .500     -.250      12.5

gfortran 16.2 rejects the four statements at compile time with the same message for every use:

13_leading_zero.f90:5:18:

    5 |   print '(a, lzp, 3f8.3)', 'lzp: ', x
      |                  1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
13_leading_zero.f90:6:18:

    6 |   print '(a, lzs, 3f8.3)', 'lzs: ', x
      |                  1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
13_leading_zero.f90:7:18:

    7 |   print '(a, lz, 3f8.3)', 'lz:  ', x
      |                  1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
13_leading_zero.f90:8:18:

    8 |   print '(a, lzs, 3es10.2, 3g10.3)', 'lzs: ', x, x
      |                  1
Error: Legacy Extension: Missing comma in FORMAT string at (1)

The message is misleading: the problem is the unknown descriptor lzp, not a missing comma. The modes can also be set for a whole connection or statement with the leading_zero= specifier, covered next.

Changeable connection modes

A connection to a formatted file has changeable modes (12.5.2) that edit descriptors consult: blank interpretation, delimiter, sign, leading zero, decimal edit, input/output rounding, pad mode, and the scale factor. The modes are established by open, or take the defaults for a preconnected unit or an internal file, and can be changed three ways:

  • by an open statement on the unit that is already connected (without file= or with the same file), which modifies the connection,

  • for one statement, by the corresponding specifier in the read, write or print-style data transfer statement (the mode takes effect at the start of the statement and is restored at its end), and

  • inside a format, by the control edit descriptor of the same name (it takes effect when format control reaches it, and the mode is again restored at the end of the statement).

Mode Specifier values Edit descriptor Default at connection Affects

Blank interpretation

blank= null, zero

bn, bz

null

nonleading blanks in numeric input fields

Decimal edit

decimal= comma, point

dc, dp

point

decimal symbol, input and output of f e en es ex d g and the list-directed separator

Delimiter

delim= apostrophe, quote, none

(none)

none

list-directed and namelist output of characters only; allowed in a data transfer statement only with * or a namelist (C1230)

Leading zero

leading_zero= print, suppress, processor_defined

lzp, lzs, lz

processor defined

optional zero in f e d g output

Pad

pad= yes, no

(none)

yes

input of a record that is shorter than the format needs

Round

round= up, down, zero, nearest, compatible, processor_defined

ru rd rz rn rc rp

processor defined

conversion of reals, input and output

Sign

sign= plus, suppress, processor_defined

sp, ss, s

processor defined

optional plus sign, output only

Scale factor

(none)

kp

0

f e d g on output and all real descriptors on input

Specifiers other than delim= require an explicit format or a namelist (C1229); pad=, blank=, decimal=, round= and sign= can therefore appear in an internal write too. The mode is restored to its previous value when the statement ends, and an inquire statement reports the current mode in the blank=, decimal=, delim=, leading_zero=, pad=, round= and sign= specifiers. The example uses an internal file to demonstrate the per-statement effect and a scratch file to demonstrate the per-connection effect:

program connection_modes
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: t = 36.256_dp, u = 0.125_dp
  character(len=40) :: buf
  character(len=20) :: mode
  integer :: unit, ios
  character(len=80) :: line

  ! A data transfer specifier changes a mode for one statement only
  write (buf, '(f8.2)', sign='plus') t
  print '(a, a)', 'sign=plus:    ', buf
  write (buf, '(f8.2)') t
  print '(a, a)', 'next write:   ', buf
  write (buf, '(f8.2)', decimal='comma') t
  print '(a, a)', 'decimal=comma:', buf
  write (buf, '(f6.2, 1x, f6.2)', round='up') u, t
  print '(a, a)', 'round=up:     ', buf
  write (buf, '(f6.2, 1x, f6.2)', round='down') u, t
  print '(a, a)', 'round=down:   ', buf

  ! An edit descriptor overrides the specifier from the point it is reached, and the mode
  ! is restored when the statement ends
  write (buf, '(f6.2, rz, f6.2, rc, f6.2)', round='up') u, u, u
  print '(a, a)', 'round mix:    ', buf

  ! A connection mode set by OPEN stays until the connection ends or another OPEN changes it.
  ! The delimiter mode affects list-directed and namelist output of characters only.
  open (newunit=unit, status='scratch', action='readwrite', decimal='comma', sign='plus', delim='quote')
  write (unit, '(f8.2, 1x, es10.2)') t, t
  write (unit, *) 'name', t
  write (unit, '(a, f8.2)', decimal='point') 'override: ', t
  inquire (unit, decimal=mode)
  print '(2a)', 'inquire decimal: ', trim(mode)
  inquire (unit, sign=mode)
  print '(2a)', 'inquire sign:    ', trim(mode)
  inquire (unit, delim=mode)
  print '(2a)', 'inquire delim:   ', trim(mode)
  rewind (unit)
  do
    read (unit, '(a)', iostat=ios) line
    if (ios /= 0) exit
    print '(2a)', 'file: ', trim(line)
  end do
  ! Another OPEN on a connected unit (no file= or the same one) can modify the modes
  open (unit, decimal='point', sign='processor_defined', delim='none')
  inquire (unit, decimal=mode)
  print '(2a)', 'after reopen decimal: ', trim(mode)
  close (unit)
end program connection_modes

gfortran 16.2:

sign=plus:      +36.26
next write:      36.26
decimal=comma:   36,26
round=up:       0.13  36.26
round=down:     0.12  36.25
round mix:      0.13  0.12  0.13
inquire decimal: COMMA
inquire sign:    PLUS
inquire delim:   QUOTE
file:   +36,26  +3,63E+01
file:  "name"  +36,256000000000000
file: override:   +36.26
after reopen decimal: POINT

Flang 23 writes the same lines except file: "name" +36,256, where gfortran writes "name" +36,256000000000000 (list-directed output of the real in both is processor dependent).

Reading the output: the first five lines are the per-statement forms. round mix shows that an edit descriptor overrides the specifier from the point it is reached: round='up' rounds the first item up (0.13), the rz descriptor then truncates the second (0.12), and rc rounds the third away from zero (0.13). The scratch file keeps decimal='comma', sign='plus' and delim='quote' for every statement on the unit; one statement that asks for decimal='point' gets a point, and the following statements get the comma again. A second open on the connected unit changes the mode, and inquire reports it.

The leading_zero= specifier, new in Fortran 2023, is accepted by Flang and rejected by gfortran 16.2 (a syntax error on the statement, for open, write and inquire). Flang 23 output for it:

! Fortran 2023: leading_zero= in OPEN and in data transfer statements (12.5.6.12, 12.6.2.10).
program leading_zero_specifier
  implicit none
  real :: x(3) = [0.5, -0.25, 12.5]
  character(len=60) :: buf
  character(len=20) :: mode
  integer :: unit

  write (buf, '(3f8.3)', leading_zero='suppress') x
  print '(2a)', 'suppress: ', buf
  write (buf, '(3f8.3)', leading_zero='print') x
  print '(2a)', 'print:    ', buf
  write (buf, '(3f8.3)') x
  print '(2a)', 'default:  ', buf

  open (newunit=unit, status='scratch', leading_zero='suppress')
  write (unit, '(3f8.3)') x
  write (unit, *) x
  inquire (unit, leading_zero=mode)
  print '(2a)', 'inquire:  ', trim(mode)
  rewind (unit)
  read (unit, '(a)') buf
  print '(2a)', 'file:     ', buf
  read (unit, '(a)') buf
  print '(2a)', 'file:     ', buf
  close (unit)
end program leading_zero_specifier
suppress:     .500   -.250  12.500
print:       0.500  -0.250  12.500
default:     0.500  -0.250  12.500
inquire:  SUPPRESS
file:         .500   -.250  12.500
file:      .5 -.25 12.5

Formatted input

Input uses the same descriptors with these rules (13.7.2 and 13.4):

  • A field is exactly w characters of the record, whatever they contain; there are no separators in formatted input. A field of blanks is zero for numeric items.

  • Implied decimal point. For fw.d, ew.d and the other real descriptors the d matters only when the field has no decimal point: 12.34 and 1234 read with f6.2 both give 12.34. This is the reason fixed-column data files can omit the point, and it surprises people who pass a free-form string to a formatted read.

  • Exponent. The field may contain an exponent of the form E+03, D+03, or just a signed digit string (15+2 is 15e2, but with f7.2 the implied decimal point is applied first, as the output below shows). Inf, Infinity and NaN (optionally followed by parentheses) are read as the IEEE values (13.7.2.3.2), and so is a hexadecimal significand.

  • Blanks. The bn mode (default) ignores nonleading blanks as though they were removed, bz reads them as zeros (13.8.7): 1 2 3 is 123 with bn and 102030 with bz.

  • Short records. With pad='yes' (the default) a record that is shorter than the format needs is padded with blanks; with pad='no' it is an error (12.6.4.5.3). The example reads the 8-character record 12 followed by blanks with i5, i5: with padding the second item is 0; with pad='no' Flang gives iostat 1003 and gfortran 16.2 does not detect the shortage for an internal file.

  • Character input. aw with a field wider than the variable keeps the rightmost characters, a narrower one pads on the right (13.7.4). a without w reads as many characters as the variable is long.

  • Logical input accepts optional blanks, an optional period, then t or f in either case, and ignores what follows.

  • A character string edit descriptor shall not be used on input (13.9). The Flang documentation lists its acceptance on input as an extension, in which case it skips as many characters as the string has.

program formatted_input
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  character(len=8) :: rec
  integer :: n, m, ios
  logical :: flag
  character(len=6) :: s
  character(len=100) :: msg

  ! Fixed fields: a field is exactly w characters, whatever the contents of the record
  rec = '12345678'
  read (rec, '(i3, i2, i3)') n, m, ios
  print '(a, 3(i0, 1x))', 'i3 i2 i3 of 12345678: ', n, m, ios

  ! The d of fw.d only matters when the field has no decimal point
  call real_field('  1234', '(f6.2)')
  call real_field('12.34', '(f6.2)')
  call real_field('1.5e3', '(f7.2)')
  call real_field('15+2', '(f7.2)')
  call real_field('1.5d3', '(f7.1)')
  call real_field(' Inf', '(f7.0)')
  call real_field('nan(7)', '(f7.0)')

  ! Blanks: the default mode is bn (ignore); bz reads them as zeros
  rec = '1 2 3'
  read (rec, '(bn, i6)') n
  print '(a, i0)', 'bn "1 2 3": ', n
  read (rec, '(bz, i6)') n
  print '(a, i0)', 'bz "1 2 3": ', n
  rec = ' '
  read (rec, '(i6)') n
  print '(a, i0)', 'blank field: ', n

  ! Logical: optional blanks, optional period, then t or f; anything may follow
  rec = '  .true.'
  read (rec, '(l8)') flag
  print '(a, l1)', 'l8 "  .true.": ', flag
  rec = 'f'
  read (rec, '(l1)') flag
  print '(a, l1)', 'l1 "f":        ', flag

  ! Character: a longer field keeps the rightmost len characters, a shorter one is padded
  rec = 'abcdefgh'
  read (rec, '(a8)') s
  print '(3a)', 'a8 into len=6: [', s, ']'
  read (rec, '(a3)') s
  print '(3a)', 'a3 into len=6: [', s, ']'
  read (rec, '(a)') s
  print '(3a)', 'a into len=6:  [', s, ']'

  ! Errors: an invalid character, and a record that is too short
  rec = '12x45'
  read (rec, '(i5)', iostat=ios, iomsg=msg) n
  print '(a, i0, 2a)', 'bad digit:             iostat=', ios, ' ', trim(msg)
  rec = '12'
  read (rec, '(i5, i5)', iostat=ios, iomsg=msg) n, m
  print '(a, i0, a, 2i4)', 'short record, pad=yes: iostat=', ios, ' n m =', n, m
  n = -1
  m = -1
  msg = ''
  read (rec, '(i5, i5)', iostat=ios, iomsg=msg, pad='no') n, m
  print '(a, i0, 2a, 2i4)', 'short record, pad=no:  iostat=', ios, ' ', trim(msg)
  print '(a, 2i4)', 'values after the error:', n, m

contains

  subroutine real_field(field, fmt)
    character(len=*), intent(in) :: field, fmt
    character(len=8) :: record
    real(dp) :: v
    record = field
    read (record, fmt) v
    print '(a, a, a, a, a, g0)', 'read ', fmt, ' of "', field, '": ', v
  end subroutine real_field
end program formatted_input

Output with gfortran:

i3 i2 i3 of 12345678: 123 45 678
read (f6.2) of "  1234": 12.340000000000000
read (f6.2) of "12.34": 12.340000000000000
read (f7.2) of "1.5e3": 1500.0000000000000
read (f7.2) of "15+2": 15.000000000000000
read (f7.1) of "1.5d3": 1500.0000000000000
read (f7.0) of " Inf": Inf
read (f7.0) of "nan(7)": NaN
bn "1 2 3": 123
bz "1 2 3": 102030
blank field: 0
l8 "  .true.": T
l1 "f":        F
a8 into len=6: [cdefgh]
a3 into len=6: [abc   ]
a into len=6:  [abcdef]
bad digit:             iostat=5010 Bad value during integer read
short record, pad=yes: iostat=0 n m =  12   0
short record, pad=no:  iostat=0
values after the error:  12   0

Flang 23 writes the same lines, except that the real values are written by g0 in its own shortest form (12.34 instead of 12.340000000000000, 1500. instead of 1500.0000000000000, 15. instead of 15.000000000000000), and these three:

bad digit:             iostat=1001 Bad character 'x' in INTEGER input field
short record, pad=no:  iostat=1003 Excessive input from fixed-size record
values after the error:  12  -1

The hexadecimal-significand input form is part of f editing (13.7.2.3.2). gfortran 16.2 rejects it at run time, and Flang accepts it:

program hex_input
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  character(len=12) :: rec
  real(real64) :: v
  integer :: ios
  character(len=100) :: msg

  ! Input of a hexadecimal-significand number is part of F editing (13.7.2.3.2)
  rec = '0x1.8p1'
  read (rec, '(f12.0)', iostat=ios, iomsg=msg) v
  print '(a, i0, a, g0)', 'iostat=', ios, ' value=', v
  if (ios /= 0) print '(a, a)', 'iomsg: ', trim(msg)
end program hex_input

gfortran 16.2:

iostat=5010 value=0.0000000000000000
iomsg: Bad value during floating point read

Flang 23:

iostat=0 value=3.

Two habits make formatted input safer. Read the record into a character variable with a, and convert it with an internal read using iostat= (see Internal Files, Namelist and Non-Advancing I/O), so that a bad field gives a status instead of ending the program; and prefer list-directed input (*) for free-form numbers, which has none of the fixed-field and implied-decimal traps.

CSV and report output with heatflow

The CSV writer of heatflow uses the unlimited format item. The rule that matters is that the separator belongs after each item except the last, because the literal in a group is written before format control looks for the next item. The colon makes that explicit: in *(g0, :, ',') the comma is written only if another item follows. Without it, *(',', i0) leaves a trailing comma. A leading text item is written once, and reversion never repeats it.

program csv
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  integer, parameter :: nx = 4
  real(dp) :: u(nx) = [0.0_dp, 36.25_dp, 0.1_dp, 1.0_dp / 3.0_dp]
  integer :: step = 200
  integer :: i
  character(len=24) :: name = 'jacobi'
  character(len=200) :: row

  ! The separator belongs after each item except the last: put it behind the item and
  ! guard it with a colon, which ends format control when the list has run out
  print '(*(a, :, ","))', 'step', 'solver', 'u1', 'u2', 'u3', 'u4'
  print '(*(i0, :, ","))', [(i, i = 1, nx)]

  ! Writing the separator in front of the item leaves a trailing separator, because the
  ! literal is written before format control looks for the next item
  print '(a, *(",", i0))', 'ids', [(i, i = 1, nx)]
  print '(a, *(",", i0))', 'ids', [integer ::]

  ! A row of mixed types with g0: the shortest field for each item. What "shortest" means
  ! for a real is processor dependent, so the compilers disagree
  print '(i0, ",", a, ",", *(g0, :, ","))', step, trim(name), u
  ! Explicit descriptors are reproducible: f0.4 has no padding but depends on the magnitude
  print '(i0, ",", a, ",", *(f0.4, :, ","))', step, trim(name), u
  ! es with a fixed width pads; trim it with adjustl in a buffer when the file must not have blanks
  write (row, '(i0, ",", a, ",", *(es15.8, :, ","))') step, trim(name), u
  print '(a)', trim(row)
  print '(a, i0)', 'length of that row: ', len_trim(row)
end program csv

Output with gfortran:

step,solver,u1,u2,u3,u4
1,2,3,4
ids,1,2,3,4,
ids,
200,jacobi,0.0000000000000000,36.250000000000000,0.10000000000000001,0.33333333333333331
200,jacobi,.0000,36.2500,.1000,.3333
200,jacobi, 0.00000000E+00, 3.62500000E+01, 1.00000000E-01, 3.33333333E-01
length of that row: 74

Flang writes the same lines except the g0 row, which is 200,jacobi,0.,36.25,.1,.3333333333333333.

The lines show the three choices for the numbers of a CSV row. The write_csv of heatflow uses the third, as (*(es12.5,:,",")):

  • g0 is the shortest text but it is processor dependent: gfortran writes all digits (0.10000000000000001), Flang the short form, and both write 0. or 0.0000000000000000 for zero. A test that compares a CSV file across compilers fails on it.

  • f0.4 has no padding and is reproducible, but f cannot represent a range of magnitudes (a value of 1e-8 is .0000) and drops the leading zero, which some parsers reject. Use it for data in a known range.

  • es15.8 is reproducible and shows eight digits after the point, at the price of a leading blank in each fixed-width field (es12.5 has the same blank, since its numbers need 11 characters; the row above is 74 characters long). Writing into a buffer and removing the blank with trim(adjustl()) per field gives clean text, and a reader that uses list-directed input skips the blanks anyway.

For a human-readable table the format is built from the row length at run time, with a ruler line. The module below is a report writer in the style of the heatflow modules; it takes the temperature field as an array:

module heatflow_report
  use, intrinsic :: iso_fortran_env, only: real64, output_unit
  implicit none
  private
  public :: print_field, row_format

  integer, parameter :: dp = real64

contains

  !> A format for one row of n values: "(i3, ' |', n(f7.2))" built at run time.
  function row_format(n, decimals) result(fmt)
    integer, intent(in) :: n, decimals
    character(len=:), allocatable :: fmt
    character(len=60) :: buf
    write (buf, '("(i3, "" |"", ", i0, "(f", i0, ".", i0, "))")') n, decimals + 5, decimals
    fmt = trim(buf)
  end function row_format

  !> Print u(i, j) as a table with a column ruler; the first index runs along a row.
  subroutine print_field(u, title, decimals, unit)
    real(dp), intent(in) :: u(:, :)
    character(len=*), intent(in) :: title
    integer, intent(in) :: decimals
    integer, intent(in), optional :: unit
    integer :: u_out, i, j, nx, ny, w
    character(len=:), allocatable :: fmt
    character(len=40) :: ruler

    u_out = output_unit
    if (present(unit)) u_out = unit
    nx = size(u, 1)
    ny = size(u, 2)
    w = decimals + 5
    fmt = row_format(nx, decimals)

    write (u_out, '(a, " (", i0, " x ", i0, ")")') title, nx, ny
    write (ruler, '("(4x, ""|"", ", i0, "(i", i0, "))")') nx, w
    write (u_out, ruler) [(i, i = 1, nx)]
    write (u_out, '(a, "+", a)') repeat('-', 4), repeat('-', w * nx)
    do j = ny, 1, -1                                  ! the hot edge is at the top
      write (u_out, fmt) j, u(:, j)
    end do
  end subroutine print_field
end module heatflow_report

program report
  use heatflow_report, only: print_field, row_format
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp) :: u(5, 4)
  integer :: i, j

  u = 0.0_dp
  u(:, 4) = 100.0_dp                                  ! hot top edge
  do j = 2, 3
    do i = 2, 4
      u(i, j) = 100.0_dp * real(j - 1, dp) / 3.0_dp * (1.0_dp - 0.1_dp * abs(i - 3))
    end do
  end do
  print '(a)', row_format(5, 1)
  call print_field(u, 'temperature', 1)
end program report

The output is the same with gfortran and Flang:

(i3, " |", 5(f6.1))
temperature (5 x 4)
    |     1     2     3     4     5
----+------------------------------
  4 | 100.0 100.0 100.0 100.0 100.0
  3 |   0.0  60.0  66.7  60.0   0.0
  2 |   0.0  30.0  33.3  30.0   0.0
  1 |   0.0   0.0   0.0   0.0   0.0

The row_format function shows the standard way to handle a row length that is only known at run time: build the format text with an internal write whose own format uses doubled quotes ("" inside the apostrophes) for the literal " |". Fortran 2023 makes this simpler because an internal write to a deferred-length allocatable character scalar allocates it to the record length (see Characters and Strings).

Summary of the edit descriptors

Descriptor Used with Notes Clause

iw, iw.m

integer, enum, enumeration

i0 narrowest; iw.0 of zero is blank

13.7.2.2

bw, bw.m, ow, ow.m, zw, zw.m

integer, real, complex, enum

bit pattern in binary, octal, hexadecimal

13.7.2.4

fw.d

real, complex

fixed point; f0.d narrowest

13.7.2.3.2

ew.d, ew.dEe

real, complex

significand in [0.1, 1)

13.7.2.3.3

dw.d

real, complex

as e with D; obsolescent in the F2028 draft

13.7.2.3.3

enw.d, enw.dEe

real, complex

engineering notation

13.7.2.3.4

esw.d, esw.dEe

real, complex

scientific notation

13.7.2.3.5

exw.d, exw.dEe

real, complex

hexadecimal significand; not in gfortran 16.2

13.7.2.3.6

gw.d, gw.dEe, g0

any intrinsic type, enum

f or e by magnitude; g0 is shortest

13.7.5

lw

logical

T or F right-justified

13.7.3

a, aw

character

width defaults to the length

13.7.4

at

character, output only

trailing blanks removed (Fortran 2023); not in gfortran 16.2

13.7.4

dt, dt'name'(v-list)

derived type

defined I/O procedure

13.7.6

tn, tln, trn, nx

 — 

position within the record

13.8.1

/, r/

 — 

end of record

13.8.2

:

 — 

stop format control when the list is exhausted

13.8.3

ss, sp, s

numeric output

sign mode

13.8.4

lzs, lzp, lz

f e d g output

leading zero mode (Fortran 2023); not in gfortran 16.2

13.8.5

kp

real

scale factor

13.8.6

bn, bz

numeric input

blank interpretation

13.8.7

ru, rd, rz, rn, rc, rp

real

rounding mode

13.8.8

dc, dp

real

decimal comma or point

13.8.9

'text', "text"

output only

character string edit descriptor

13.9

Deleted and nonstandard features

A format is also where the history of Fortran is most visible. Annex B.1 of the standard lists the features deleted from Fortran 90, among them three that live in formats: the H edit descriptor (5Hhello, the Hollerith form of a string descriptor), assigned format specifiers (assign 10 to fmt, then write (*, fmt)), and vertical format control, the interpretation of the first character of a record as a printer carriage control (1 for a new page, + for overprint, 0 for a blank line). Old programs that have H descriptors still compile with a flag or an extension:

program h_descriptor
  implicit none
  write (*, '(5Hhello, 1x, a)') 'world'
  write (*, 10) 'x'
10 format (6HHeat: , a)
end program h_descriptor

gfortran 16.2 with -std=f2023 rejects both uses (the third message is a consequence of the first, because the label does not exist):

20_h_descriptor.f90:3:14:

    3 |   write (*, '(5Hhello, 1x, a)') 'world'
      |              1
Error: The H format specifier at (1) is a Fortran 95 deleted feature
20_h_descriptor.f90:5:13:

    5 | 10 format (6HHeat: , a)
      |             1
Error: The H format specifier at (1) is a Fortran 95 deleted feature
20_h_descriptor.f90:4:19:

    4 |   write (*, 10) 'x'
      |                   1
Error: FORMAT label 10 at (1) not defined

With -std=gnu gfortran accepts the program and prints hello world and Heat: x, with a warning for each use that the feature is deleted; with -std=f95 it reports the same text as an error. Flang 23 accepts the program without a diagnostic and prints the same two lines. The replacement is a character string edit descriptor ('hello'). For vertical format control, no compiler of the two interprets the first character: the output of the following program has the first character of each record as written:

program first_column
  implicit none
  ! the first character of a record has no special meaning in Fortran 2023
  print '(a)', '1 this would have started a new page'
  print '(a)', '+ this would have overprinted'
  print '(a)', '0 and this would have double spaced'
end program first_column
1 this would have started a new page
+ this would have overprinted
0 and this would have double spaced

Flang’s output is identical. Both compilers also have extensions accepted in formats that -std=f2023 rejects or that exist only in one of them. The program below exercises four; gfortran rejects each at compile time under -std=f2023, and Flang accepts them all:

program extensions
  implicit none
  integer :: n = 41
  real :: x = 1.5
  print '(i)', n
  print '(f)', x
  print '(a, $)', 'no newline after this'
  print '(l0)', .true.
end program extensions

Flang 23 output (the $ descriptor suppresses the newline, so the T of the next l0 follows the text on the same line):

41
1.5
no newline after thisT

gfortran 16.2 stops at compile time with a diagnostic for each of the four (the two Nonnegative width lines are (i) and (f)):

27_extensions.f90:5:10:

    5 |   print '(i)', n
      |          1
Error: Nonnegative width required in format string at (1)
27_extensions.f90:6:10:

    6 |   print '(f)', x
      |          1
Error: Nonnegative width required in format string at (1)
27_extensions.f90:7:7:

    7 |   print '(a, $)', 'no newline after this'
      |       1
Error: GNU Extension: $ descriptor at (1)
27_extensions.f90:8:11:

    8 |   print '(l0)', .true.
      |           1
Error: Extension: Zero width after L descriptor at (1)

-fdec-format-defaults makes gfortran accept descriptors without width, with a default width per kind. The next program writes an integer with i, and a real with f and with g, all without width:

program dec_defaults
  implicit none
  integer :: n = 41
  real :: x = 1.5
  print '(i)', n
  print '(f)', x
  print '(g)', x
end program dec_defaults

built with gfortran -fdec-format-defaults prints (blanks shown as underscores) the following, while Flang prints 41, 1.5 and 1.5:

__________41
______1.5000000
___1.500000____
Extension gfortran 16.2 Flang 23

Data edit descriptor without width ((i), (f), (g))

error Nonnegative width required; -fdec-format-defaults selects default widths (i is 12 characters wide)

accepted, minimal width (41, 1.5)

$ descriptor to suppress the newline

error GNU Extension: $ descriptor under -std=f2023, accepted with -std=gnu

accepted

l0, zero width for logical output

error Extension: Zero width after L descriptor under -std=f2023, accepted with -std=gnu

accepted

a with a noncharacter item

accepted silently

accepted silently

The gfortran options are described in the manual under Options controlling Fortran dialect and Extensions implemented in GNU Fortran; the Flang ones in Flang extensions. The non-advancing alternative to $ is the advance='no' specifier, covered in the next page of the section. The standard way to write a prompt without a newline, a zero-width logical (l1) and an explicit width on every descriptor remove the need for any of them.

Where older references are outdated

Texts written before Fortran 2003, and many online snippets, teach a view of formats that no longer holds:

  • H descriptors, assigned formats and carriage control are gone (Annex B.1), though old compilers and some libraries still accept them. Current code uses string descriptors, character variables for formats and no first-column convention.

  • D is the double precision descriptor. There has been no such thing since Fortran 90 allowed more than two real kinds; e and es work for every kind, and dw.d is obsolescent in the Fortran 2028 draft.

  • g0, ex, dt and the unlimited format item are missing from older references, and at, lzp, lzs, lz and the leading_zero= specifier are Fortran 2023 additions (the Introduction of the standard lists them). Compiler support for the newest ones is incomplete: Flang 23 accepts lz* and leading_zero= and, although its documentation says otherwise, at; gfortran 16.2 accepts none of them, and does not implement ex.

  • err=, end= and eor= are not obsolescent, whatever some references say; iostat= and iomsg= are the preferred style, and they are what the examples above use to catch format errors.

  • Formatted I/O is slow, and a binary checkpoint is better kept in an unformatted stream file; the CSV in this page is a format for exchange, not for storage.

Compiler options and diagnostics

Neither compiler detects a type mismatch between a literal format and the I/O list at compile time; both report it at run time (see When the format and the list disagree). -fcheck=all does not add format checks. What helps in practice:

  • A literal format is checked for syntax at compile time by both compilers, so the examples with a missing parenthesis or an unknown letter are caught. Keep formats literal (or parameter) when possible.

  • Use iostat= and iomsg= on every formatted read of external data and report iomsg on failure, as the examples do.

  • Compile with -std=f2023 (gfortran) to turn the extensions of the previous section into errors. Flang accepts many extensions without a diagnostic (its Extensions page lists them), so a program that builds with Flang is not thereby standard-conforming.

  • The text of the messages is compiler specific and not part of the standard, and so are the iostat values; test with iostat /= 0 and use is_iostat_end and is_iostat_eor for the two standard conditions (12.11).