Fortran provides the following intrinsic data types: numeric types integer
,
real
and complex
; non-numeric types logical
and character
.
The following program declares a variable with each of the three intrinsic numeric types, and provides an initial value in each case.
program example1
implicit none
integer :: i = 1 ! A default integer kind
real :: a = 2.0 ! A default floating point kind
complex :: z = (0.0, 1.0) ! A complex kind with (real-part, imag-part)
end program example1
Initial values are optional. If a declaration does not specify an initial value, the variable is said to be undefined.
The valid Fortran character set for names is a-z
, A-Z
, 0-9
and the
underscore _
. Valid names must begin with a character. The maximum
length of a name is 63 characters (F2003) with no spaces allowed.
This includes names for programs, and names for variables.
Other special characters recognised by Fortran are given in F-SPECIAL-CHARACTERS.md.
The implicit
statement defines a type for variable names not explicitly
declared. So, the default situation can be represented by
implicit integer (i-n), real (a-h, o-z)
that is, variables with names beginning with letters i-n
are implicitly
of type integer
, while anything else is of type real
(unless
explicitly declared otherwise).
By modern standards this is tantamount to recklessness. The general solution to prevent errors involving undeclared variables (usually arising from typos) is to declare that no names have implicit type via
implicit none
In this case, all variable names must be declared explicitly before they are referenced.
However, it is still common to see the idiom that variables beginning
with i-n
are integers and so on.
Compile and run the accompanying program exercise1.f90
. What's
the problem and how should we avoid it? Check the compiler can trap
the problem.
The declarations above give us variables of some (implementation-defined)
default type (typically 4-byte integers, 4-byte reals). A mechanism to
control the exact kind, or representation, is provided. For example
(see example2.f90
),
use iso_fortran_env, only : int64, real64
implicit none
integer (kind = int64) :: i = 100
real (kind = real64) :: a = 1.0
complex (kind = real64) :: z = (0.0, 1.0)
Here we use kind type parameters int64
and real64
to specify that
we require 64-bit integers and 64-bit floating point storage, respectively.
A standard conforming Fortran implementation must provide at least one
integer
precision with a decimal exponent range of at least 18 (experience
suggests all provide at least two kinds). Note that Fortran does not have
any equivalent of C/C++ unsigned integer types.
An implementation must provide at least two real
precisions, one default
precision, and one extended precision (sometimes referenced as
double precision
).
Formally, the numeric types are introduced
numeric-type-spec [kind-selector] ...
where the numeric-type-spec
is one of integer
, real
, or complex
.
The optional kind selector is
([kind = ] scalar-integer-initialisation-expr)
The upshot of this is that the syntax of declarations is quite elastic, and you may see a number of different styles. A reasonable form of concise declaration with an explicit kind type parameter is:
integer (int32) :: i
real (real32) :: a
complex (real32) :: z
Take a moment to compare the output of the programs example1.f90
and
example2.f90
, which declare and initialise a variable of each type and
print out their values to the screen.
One may (optionally) specify the kind of an integer literal by appending the
kind type parameter with an underscore _
, e.g.:
123
+123
-123
12345678910_int64
Floating point literal constants can take a number of forms. Examples are:
-3.14
.314
1.0e0 ! default precision
1.0d0 ! extended (double) precision
3.14_real128 ! may be available
3.14e-1 ! Scientific notation
3.14d+1 ! Scientific notation extended precision
Complex literals are constructed with real and imaginary parts, with each part real.
(0.0, 1.0) ! square root of -1
Suppose we did not want to hardwire our kind type parameters throughout the code. Consider:
program example3
implicit none
integer, parameter :: my_e_k = kind(1.e0)
integer, parameter :: my_d_k = kind(1.d0)
real (my_e_k) :: a
real (my_d_k) :: b
! ...
end program example3
Here we have introduced an integer
with the parameter
attribute. This is
an instruction to the compiler that the associated name should be a constant
(similar to a const
declaration in C). Any subsequent attempt to assign a
value to a parameter will result in a compile time error.
The value specified in the parameter declaration must be a constant expression
(which the compiler may be able to evaluate at compile time). Here we have made
use of the intrinsic function kind()
. The kind()
function returns an
integer
which is the kind type parameter of the argument. In this context a constant
expression is one in which all parts are intrinsic.
Note we have not included anything or use
'd anything to make this kind()
function available.
It is an intrinsic function and part of the language itself.
Using a parameter provides a degree of abstraction for the real data type. Other examples might include:
integer, parameter :: nmax = 32 ! A constant
real, parameter :: pi = 4.0*atan(1.e0) ! A well-known constant
complex, parameter :: zi = (0.0, 1.0) ! square root of -1
The intrinsic function atan()
returns the inverse tangent (as a value in
radians) of the argument.
Consider further the accompanying example3.f90
, where we have introduced
another intrinsic function storage_size()
(roughly equiavalent to C
sizeof()
although it returns a size in bits rather than bytes).
Run the program and check the actual values of the kind type parameters
and associated storage sizes. Speculate on the portability of a program
declaring, e.g.,:
integer (4) :: i32
integer (8) :: i64
Consider the accompanying exercise2.f90
. Check the compiler error emitted and
remove the offending line.
For all intrinsic numeric types, standard arithmetic operations,
addition (+
), subtraction (-
), multplication (*
) and
division (/
) are defined, along with exponents (**
) and
negation (-
).
In order of increasing precedence these are -
, +
, /
, *
,
and **
(otherwise left-to-right). In particular
a = b*c**2 ! is evaluated as b*(c**2)
a = b*c*d ! evaluated left-to-right (b*c)*d
Use parentheses to avoid ambiguity if necessary.
A large number of intrinsic functions exist for basic mathematical
operations and are relevant for all numeric types. A simple example
is sqrt()
. Some are specific to particular types, e.g., conjg()
for complex conjugate.
Broadly, assignments featuring different data types will cause promotion to a "wider" type or cause truncation to a "narrower" type. If one wants to be explicit, the equivalent of the cast mechanism in C is via intrinsic functions, e.g.,
integer :: i = 1
complex (real64) :: z = (1.0, 1.0)
real (real64) :: a, b
a = real(i, real64) ! a should be 1.d0
a = real(z, real64) ! imaginary part is ignored
b = real(i) ! return default real kind
z = cmplx(a, b) ! (sic) Form complex number from two reals
The second argument of the real()
function is optional, and specifies the
kind type parameter of the desired result. If the optional argument is not
present, then a real value of the default kind is returned.
Note here the token real
has been used in two different contexts: as
a statement in the declaration of variables a
and b
, and as a function.
There is not quite the same concept of "reserved words" (cf C/C++);
lines are split into tokens based on spaces, and tokens are parsed in
context.
The real and imaginary parts of a complex variable may be accessed
complex :: z
z%re = 0.0 ! real part
z%im = 1.0 ! imaginary part
where the %
symbol is referred to as the component selector. The real
and imaginary parts are also available via the real()
and aimag()
intrinsic functions, respectively.
By using variables of complex type, check that you can use the
intrinsic function srqt()
to confirm that the square root of
-1 is i
. What happens if you try to try to take the square root
of a negative value stored as a real variable?
The accompanying template exercise3.f90
provides instructions for an
exercise which involves the approximation to the constant pi computed
via a Gauss-Legendre algorithm. Some
background can be found at https://en.wikipedia.org/wiki/Gauss-Legendre_algorithm.
A solution to this problem appears as the template for the exercise in section2.01.
A second exercise in a similar vein looks at an approximation to the
conductance of a rectangular channel subject to a constant flow.
Instructions are in exercise4.f90
.
A solution to this problem appears as the template for the second exercise in section2.01.