Variables
Overview
Teaching: 10 min
Exercises: 20 minQuestions
What are some basic types of numeric variables in Fortran?
How are variables declared and defined?
How do I create constants?
Objectives
Understand how to create variables and assign values to them.
Specify the representation in memory of variables independently of the implementation.
Store constant values.
Numeric variables of intrinsic type
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.
Variable names
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:
Character | Name | Character | Name |
---|---|---|---|
Blank | ; |
Semi-colon | |
= |
Equals | ! |
Exclamation mark |
+ |
Plus | " |
Double quote |
- |
Minus | % |
Percent |
* |
Asterick | & |
Ampersand |
/ |
Slash | ~ |
Tilde |
\ |
Backslash | < |
Less than |
( |
Left parenthesis | > |
Greater than |
) |
Right parenthesis | ? |
Question mark |
[ |
Left square bracket | ' |
Apostrophe |
] |
Right square bracket | ` |
Grave accent |
{ |
Left curly brace | ^ |
Circumflex |
} |
Right curly brace | \| |
Pipe |
, |
Comma | $ |
Dollar sign |
. |
Decimal point | # |
Hash |
: |
Colon | @ |
At |
Other characters may appear in comments.
implicit
statement
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, albeit declared explicitly.
Using
implicit none
To reiterate – all modern Fortran code really should use
implicit none
. Failing to do so will almost certainly lead to bugs and hence time spent debugging!
Exercise (1 minute)
The importance of being explicit
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.
Solution
The issue is that the variable we declare is initialise is
ll
, but the variable we print isl1
. Look closely at these names: the latter mistakenly uses a digit one1
where it should be the letterl
. When we print the hitherto unmentionedl1
, the compiler implicitly creates it as a newinteger
.Add
implicit none
to the top of the codeprogram exercise1 implicit none integer :: ll = 1 print *, "The value of ll is: ", l1 end program exercise1
which then produces an error at compile time, e.g. with the Cray compiler:
print *, "The value of ll is: ", l1 ^ ftn-113 ftn: ERROR EXERCISE1, File = exercise1.f90, Line = 7, Column = 36 IMPLICIT NONE is specified in the local scope, therefore an explicit type must be specified for data object "L1". Cray Fortran : Version 15.0.0 (20221026200610_324a8e7de6a18594c06a0ee5d8c0eda2109c6ac6) Cray Fortran : Compile time: 0.0024 seconds Cray Fortran : 9 source lines Cray Fortran : 1 errors, 0 warnings, 0 other messages, 0 ansi Cray Fortran : "explain ftn-message number" gives more information about each message.
kind
of type
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
Example (2 minutes)
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.
Numeric literal constants
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
Parameters
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.
Exercise (2 minutes)
Consider further the accompanying example3.f90, where we have introduced
another intrinsic function storage_size()
(roughly equivalent to C’s
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
Exercise (2 minutes)
Parameters
Consider the accompanying exercise2.f90. Check the compiler error emitted and remove the offending line.
Solution
The problem is that
nmax
is created with theparameter
attribute and given a value of 32, but that later on an attempt is made to assign a new value of 33.nmax = 33
to resolve the issue.
Arithmetic operations on numeric types
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.
Complex real and imaginary parts
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.
Exercise (2 minutes)
Using
sqrt()
By using variables of complex type, check that you can use the intrinsic function
srqt()
to confirm that the square root of -1 isi
. What happens if you try to try to take the square root of a negative value stored as a real variable?Solution
If you call
sqrt()
on a complex variablez%re = -1.0
andz%im = 0.0
then a result will be returned with a real part of0
and an imaginary part of1
.If you call
sqrt()
on a real number-1
then the compiler will exit with an error and a message explaining why this is not valid.
Exercise (5 minutes)
Calculating pi
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.
Use the instructions in the template to calculate an estimate of pi by creating an appropriate parameter to store a
kind
, declaring the appropriate variables, then performing the calculation and printing the values of pi.Solution
A solution to this problem appears as the template for the first exercise in the episode on loops.
Exercise (5 minutes)
Calculating the conductance in a narrow channel
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.
As with the previous exercise, create a
kind
, the variables, then perform the necessary arithmetic to calculateC_1
.Solution
A solution to this problem appears as the template for the second exercise in the episode on loops.
Key Points
Fortran provides the intrinsic numeric types of
integer
,real
andcomplex
.Without a
kind
, these types are implementation-defined. Usekind
to specify the representation of variables.The
iso_fortran_env
intrinsic module provides standard kinds such asreal32
andreal64
.Always use
implicit none
to prevent the accidental implicit declaration of new variables.Use the
parameter
attribute to make the value associated with the name constant.