Detect formatting errors in write()

Consider the following code:

program test_err_fmt_write

integer :: iostat

write(*, *, iostat=iostat) "(f3.2)", 0.1
write(*, *) "iostat=", iostat
write(*, "(f3.2)", iostat=iostat) 10000.1
write(*, *) "iostat=", iostat

end program

This outputs:

.10
 iostat=           0
***
 iostat=           0

The *** is expected, since 10000.1 cannot be contained in a “(f3.2)” format.

But shouldn’t iostat be non-zero here, since there is a formatting error?

Is there a way to detect the formatting did not work?

No, this is not a formatting error as the behaivior is defined by the standard. In fact, I use just this feature to visually distinguish occupied from unoccupied elements in an array.

HTH

Mike

Oh, I see, a formatting error occurs when a format specifier is wrong, e.g. (xyz) instead of (f3.2), but not when a value doesn’t fit in a format.

This is troublesome, as it can lead to writing invalid values to a file without warning. I guess I’ll have to check the range of the input value before formatting.

BTW, what do you mean by “occupied”? Would you mind giving a small example?

You can always use an E format (edit descriptor) - that will accommodate any (ordinary) number. You can also use the G format, that will switch between fixed-decimals and exponential notation.

A contrived problem involves 5 3x3 arrays linked by various relationships that can best be represented by enclosing them within a 9x9 array initialised to a large value. The 4 3x3 subarrays of the 9x9 array that are not part of the problem are ‘unoccupied’ (which is different to ‘undefined’). Visually, using an i2 descriptor, it might look like this:

 1 7 0****** 0 0 0
 0 0 4****** 6 2 1
 0 2 0****** 7 0 0
****** 2 0 6******
****** 5 0 7******
****** 8 0 0******
 7 0 0****** 0 1 2
 0 1 2****** 3 0 0
 0 0 0****** 0 0 7

Of course, with a text editor the asterisks might br replaced by blanks, or whatever.

Mike

1 Like

If you have a reason to not use a more accommodating format descriptor you can add a range check to the values to be printed to make sure they will fit as before printng them.

Depending on the compiler sometimes it can get tricky to test for infinities and NaNs. So another possibility is go ahead and write the output; but internally to a string. Then you can inspect the string any way you want and maybe reprint with
another format or take whatever action you need to.

program test_err_fmt_write

integer :: iostat
character(len=132) :: line

write(line, *, iostat=iostat) "(f3.2)", 0.1
write(*, *) trim(line)
if(index(line,'*').ne.0)write(*,*)'overflowed'
write(*, *) "iostat=", iostat

write(line, "(f3.2)", iostat=iostat) 10000.1    ! internal write
write(*, *) trim(line)
if(index(line,'*').ne.0)write(*,*)'overflowed'  ! inspect line
write(*, *) "iostat=", iostat

end program

A little more sophisticated is to convert your values to strings via a function.
The function can decide which format is required by looking at the magnitude of the number and select between various ones. If none is suitable it could warn about the magnitude and quit or whatever.,

Regarding descriptors,
(f0.2) might be nice in this case if you have not tried “f0” before.

It frustrates me when a calculation that has run for a long time returns output with ***. I know scientific notation formats are available but prefer the fw.d format. It would be nice to have a generalized f format that uses fw.d when possible but switches to scientific notation otherwise. One could write a function to do this:

module format_gen_mod
implicit none
integer, parameter :: dp = kind(1.0d0)
contains
pure function str(x, fmt) result(s)
real(kind=dp), intent(in) :: x
character (len=*), intent(in), optional :: fmt
integer, parameter :: nlen = 20
character (len=nlen) :: s
character (len=100) :: fmt_
if (present(fmt)) then
   fmt_ = fmt
else
   fmt_ = "(f12.6)"
end if
write (s, fmt_) x
if (s(1:1) == "*") write (s, "(es12.6)") x
end function str 
end module format_gen_mod

program xformat_gen
use format_gen_mod
implicit none
real(kind=dp) :: x
real(kind=dp), parameter :: mult = 1.0d6
integer :: i
character (len=12) :: s
x = 3.14159/mult
do i=1,3
   write (s, "(f12.6)") x
   print "(*(a12, 1x))", s, str(x)
   x = mult*x
end do
end program xformat_gen

output:

    0.000003     0.000003
    3.141590     3.141590
************ 3.141590E+06
1 Like

It would be nice to have a format that “never fails” indeed, in the cases were exact length of the output string does not matter (e.g. to write values on a text file separated by space: parsers just split the line at each whitespace, and don’t care that value length varies).

1 Like

I also think, for free-form data files (in which spaces are used to delimit numbers), C or python-like formatting is nicer because it automatically adjusts the width when necessary. (I guess the F and G descriptors in Fortran was designed much ago for fixed-form data files, like Protein Data Bank (PDB) format etc.) I think a flexible format (like cf10.4) would be useful if available (in addition to f0.4, which is also very useful for free format).

program main
    use iso_fortran_env, only: dp => real64
    implicit none
    real(dp) :: x
    integer :: n
    x = acos(-1.0_dp)

    do n = -5, 9
        print "('n = ', i3, ' x * 10**n = ', f10.4)", n, x * 10.0_dp**n
        !! print "('n = ', i3, ' x * 10**n = ', g10.4)", n, x * 10.0_dp**n
        !! print "('n = ', i3, ' x * 10**n = ', e10.4)", n, x * 10.0_dp**n
        !! print "('n = ', i3, ' x * 10**n = ', es10.4)", n, x * 10.0_dp**n
    enddo
end

Output:

format = f10.4
n =  -5 x * 10**n =     0.0000
n =  -4 x * 10**n =     0.0003
n =  -3 x * 10**n =     0.0031
n =  -2 x * 10**n =     0.0314
n =  -1 x * 10**n =     0.3142
n =   0 x * 10**n =     3.1416
n =   1 x * 10**n =    31.4159
n =   2 x * 10**n =   314.1593
n =   3 x * 10**n =  3141.5927
n =   4 x * 10**n = 31415.9265
n =   5 x * 10**n = **********
n =   6 x * 10**n = **********
n =   7 x * 10**n = **********
n =   8 x * 10**n = **********
n =   9 x * 10**n = **********

format = g10.4
n =  -5 x * 10**n = 0.3142E-04
n =  -4 x * 10**n = 0.3142E-03
n =  -3 x * 10**n = 0.3142E-02
n =  -2 x * 10**n = 0.3142E-01
n =  -1 x * 10**n = 0.3142    
n =   0 x * 10**n =  3.142    
n =   1 x * 10**n =  31.42    
n =   2 x * 10**n =  314.2    
n =   3 x * 10**n =  3142.    
n =   4 x * 10**n = 0.3142E+05
n =   5 x * 10**n = 0.3142E+06
n =   6 x * 10**n = 0.3142E+07
n =   7 x * 10**n = 0.3142E+08
n =   8 x * 10**n = 0.3142E+09
n =   9 x * 10**n = 0.3142E+10

In the case of Python:

import numpy as np
x = np.pi

for n in range(-5, 10):
    print( f"{n = :3d}  {x * 10**n = :10.4f}" )

Output:

n =  -5  x * 10**n =     0.0000
n =  -4  x * 10**n =     0.0003
n =  -3  x * 10**n =     0.0031
n =  -2  x * 10**n =     0.0314
n =  -1  x * 10**n =     0.3142
n =   0  x * 10**n =     3.1416
n =   1  x * 10**n =    31.4159
n =   2  x * 10**n =   314.1593
n =   3  x * 10**n =  3141.5927
n =   4  x * 10**n = 31415.9265
n =   5  x * 10**n = 314159.2654
n =   6  x * 10**n = 3141592.6536
n =   7  x * 10**n = 31415926.5359
n =   8  x * 10**n = 314159265.3590
n =   9  x * 10**n = 3141592653.5898

Try f0.4 instead of f10.4; f0 and g0 and i0 provide very much the same as list-directed output with more control if desired. And you do not get arbitrary line breaks and whitespace that list-directed I/O is free to select in various ways.

1 Like