Vous êtes sur la page 1sur 13

I/O (F book, chapters 9, 10 and 15)

All I/O in Fortran90 is record-based, typically with


record delimiters of some kind. This is in contrast to C,
which has stream I/O, with no record delimiters required.
First lets look at I/O to the terminal (console I/O).
Such output is typically formatted. Formatted means there
is a transformation from the internal representation to
printed characters (typically ascii). The easiest way to
write output is to let the compiler figure out the formatting
for you, as follows:
integer :: i = 3
! stored as ...0011 in binary
write (unit=*,fmt=*) i = , i
! will print: i = 3
The unit=* is always preconnected to the terminal, and
fmt=* means to use the default formatting. This kind of
output is called list-directed.
Every write statement creates a single record. For
formatted output, the record is automatically terminated
by some kind of new line character, typically either CR, LF,
or CR-LF, depending on the computer.
The unit=6 is also pre-connected to the console, but it
is a different unit than *. Unit 6 can be redirected to a file,
unit=* cannot.

With list-directed output, the compiler choose the


format. This is great for quick and simple output, but not
so great if you want pretty output, such as tables. Fortran
has format specifiers and edit descriptors to control the
appearance of output with great precision.
I will not go into the intricate details here, but merely
summarize the general features. The most useful edit
descriptors are
aw for character variables
iw for integer variables
fw.d for real variables
ew.d for real variables with exponential format
nx for n spaces
Where w is the width of output characters, and d is the
number of characters after the decimal. For example,
real :: a = 4.2
complex :: z = (1.0e-10,-2.0e-5)
character(len=32) :: c = '(1x,a,1x,i4,1x,f4.2,1x,2(e14.7))'
write (*,c) 'variables:', i, a, z
will print:
variables:

3 4.20 0.1000000E-09-0.2000000E-04

If the variable is too long for the w field requested, *** will
be printed. Note for characters, you can specify just a
instead of aw, and it will figure out what w to use.

There is also a generalized edit descriptor, which can be


used for any intrinsic type. It is useful for making tables
where each column is the same size:
character(len=32) :: c = '(1x,5g10.2)'
write (*,c) 'variables:', i, a, z
will print:
variables:

3 4.2

0.10E-09 -0.20E-04

Another useful edit descriptor is Zw for hexadecimal. This


is useful for examining binary data. For example, how
exactly is pi internally represented on your computer?
real :: pi = 3.141596253589793
write (*,'(Z8)') pi
On my Macintosh, the result is:
40490fea

Another useful feature is non-advancing I/O. E.g.,


write (unit=*,fmt='(a)',advance='no') 'enter input:'
read (*,*) i
will suppress the carriage return and/or line feed at the
end of the prompt, so that if I enter 5, the console output
looks like:
enter input:5
List-directed reads are very convenient and
recommended. The following will read from the keyboard:
read (unit=*,fmt=*) i , j

! converts ascii input to binary

Input values should be separated by commas or blanks.

Besides writing to the console, one often needs to


write to a file. In Fortran, all files are described by an
integer unit number, which is connected to a file name with
an open statement:
For example, to print formatted output to a file:
integer : iu = 12
open(unit= iu,file=filename, form=formatted)
where filename is the name of the file you are writing to.
You may not connect two units to the same file, or the
same file to 2 units.
It is also possible to write unformatted (binary) output.
One does this by opening a file as unformatted:
open(unit= iu,file=filename, form=unformatted)
and then omitting the format specifier in the read
statement, as follows:
integer :: i = 3
write (unit=iu) i

! stored as ...0011 in binary


! will write: ...0011 in binary

The output will be written using the internal binary


representation of the data. Since storage of binary
numbers is machine dependent, and one generally cannot
expect to read such a file on a different computer than the
one it was written on.

Since Fortran is record based, each write statement


determines a record. Thus
write (unit=iu) i, j
write (unit=iu) k, l

! writes first record


! writes second record

writes two records. Unlike formatted output, where


records are terminated with CR or LF characters,
unformatted files usually add additional internal
information to the file, typically the length of the record
before and after each write. The purpose of this is to
support partial record reads. For example, after writing
the above data, one can read it as follows:
rewind iu
read (unit=iu) m
read (unit=iu) n

! will read the value i only


! will read the value k only

where only the first word of each record is read. One can
also skip over a record by just reading nothing
read (unit=iu)

! skips over this record to next one

or go back to a previous record by backspacing:


backspace iu

! go to previous record

The internal information needed to support this is not


specified, and each vendor can implement it as they wish.
Thus programs compiled by different compilers may
produce files which are not compatible, even on the same
machine.

There is a special record called EOF (End of File) which


is written at the end of a file when the file is closed:
close(unit=iu)
It is possible to detect errors in reading files by using
the iostat keyword on a read statement:
integer :: ierror
read(unit=iu,iostat=ierror)
If an error occurs, the variable ierror will be non-zero.
Your program can use this feature to determine how many
records in a file:
integer :: num = 0
do
read(unit=iu,iostat=ierror) i
if (ierror /= 0) exit
num = num + 1
enddo
write (*,*) number of records in file = , num
Fortran does not have any standard way to ensure that a
file is actually written to disk (and not just buffered).
However, the following sequence of instructions:
end file iu
backspace iu
often works on many compilers.

The kinds of files we have been discussing so far are


called sequential files. They were originally magnetic
tapes, which had to be read sequentially from one end to
another. With disks, however, it is possible to jump to
different locations directly. Fortran therefore also
supports something called direct access files. Such files
require each record to be the same length. One opens
such a file as follows:
integer :: recordlength
open(unit= iu,file=filename, form=formatted,
recl=recordlength,access=direct)
where recordlength is the size of each record. One reads
such files by specifying a record number, as follows:
integer :: rec_num = 1
read (unit=iu,fmt=*,rec=rec_num) i

! read first record

The units of the record length are bytes for formatted files,
but are in unspecified vendor dependent units for
unformatted (binary) files. To determine what units to
use, one uses a special inquire function. For example to
open a direct access file where each record is big enough
to write an integer:
integer :: i
inquire(iolength=recordlength) i
open(unit= iu,file=filename, form=unformatted,
recl=recordlength,access=direct)

One can emulate stream I/O by creating a direct access


file with a very large record length, and using with the
advance=no keyword, as follows:
real, dimension(100) :: data
write (unit=iu,fmt='(a)',rec=1,advance='no') data

There are many other keywords which one can use


when opening files, such as
status: old,new,replace,unknown,or scratch
action: read,write, or readwrite
position: rewind, or append
There are also a number of useful inquiry functions. One
can inquire whether a file exists, whether it is opened,
whether a unit number is connected. For example,
logical :: connected
inquire(unit=iu,opened=connected)
The variable connected will be true if the unit number iu is
already connected to a file.
All of the file names here are relative to the current
directory (or one can specify a full path name). There are
no intrinsics to change the current directory or create a
directory.

In addition to writing to a file, one can write to a


character variable, which is known as an internal file, as
follows:
character(len=80) :: c
integer :: i = 3
write (unit=c,fmt=*) i= , i
The variable c now contains the string: i= 3.
One can also read from an internal file. After execution,
the statement:
character(len=80) :: d
integer :: j
read (c,*) d, j
will set the string d to the value i=, and the variable j to 3.
This allows one to convert from binary to ascii and
vice-versa. It is useful to passing formatted information to
other procedures, such as to a graphics subroutine for
labeling of scientific output.

Advanced features (F book, chapter 16)


One interesting new feature of Fortran90 is the
optional argument in a procedure. Suppose we had an
FFT procedure, where we had to reinitialize the sinecosine tables only when the size of the FFT had changed.
This could be implemented as follows:
subroutine fft(data,direction,initialize)
real, dimension(:), intent(inout) :: data
integer, intent(in) :: direction
logical, intent(in), optional :: initialize
...
if (present(initialize)) then
if (initialize) call init_table(size(data))
endif
The present intrinsic is used to test whether an optional
argument is actually present.
Normally, a forward transform is performed as:
real, dimension(128) :: f
call fft(f,1)
! just perform fft
However, if the tables had to be reinitialized, we would
add an additional argument.
call fft(f,1,.true.)

! reinitialize tables and perform fft

One feature which makes for better readability is the


use of keywords. For example, one can write the fft call as
follows:
call fft(data=f,direction=1,initialize=.true.)
If edifying names are chosen for the subroutine variables,
using keywords makes it clearer what the programmer
intended. It is possible to use mix keyword variables and
positional variables. For example,
call fft(f,1,initialize=.true.)
is permitted. If keywords are used, they can be in any
order. For example,
call fft(initialize=.true.,direction=1,data=f)
is permitted.

Suppose we have two procedures for performing 1d


and 2d FFTs:
real, dimension(128) :: f
real, dimension(32,32) :: g
call fft1d(f,1)
call fft2d(g,2)

! perform 1d fft
! perform 2d fft

If the types used by the two procedures differ, one can


create a single name which is used in both instances:
call fft(f,1)
call fft(g,2)

! perform 1d fft
! perform 2d fft

where the compiler figures out which name to use at


compile time. Such procedures are called generic
procedures, and are created as follows:
module fft_module
interface fft
! create generic fft name
module procedure fft1d, fft2d
end interface
contains
subroutine fft1d(data,direction,initialize)
real, dimension(:), intent(inout) :: data
...
subroutine fft2d(data,direction,initialize)
real, dimension(:,:), intent(inout) :: data
...
end module fft_module

Vous aimerez peut-être aussi