This is meant to be a practical introduction to Fortran programming. The best way to learn programming is to jump right in. We will look at concrete examples that do useful things. Some topics will be used before they have been properly defined, but you should be able to grasp the sense of what is going on, and you may expect the explanation to come shortly thereafter.
Before you can do anything with Fortran, you need to have access to a computer, have an account, know how to use an editor, and so on. I'm not going to touch these topics, but our first exercise will test whether you have mastered them on your own so that we can move past these details and get into Fortran. Our goal in this first exercise is to do "whatever it takes" to create, compile, and run a "Hello, world!" program.
You will need to create a file something like this:
program hello print *, 'Hello, world!' stop end program
On my (UNIX) system, I can run this program with the commands:
f90 hello.f90which should cause the appearance of the message:
a.out
Hello, world!Do what you have to do until you can get this program to work on your system.
This program is written in Fortran90. You might have run across programs written in earlier versions of Fortran, which required that most statements begin after column 6. While we won't be using such versions here, you should be aware that this was the old style!
The program begins with a PROGRAM statement which includes a "name" for the program. The program ends with an END statement. Neither of these routines "does" anything, but they are important in defining the structure of the program.
The "work" of the program is carried out by the PRINT statement. You may be familiar with the more complex but versatile WRITE statement, which you could use here as
write ( *, * ) 'Hello, world!'.
The STOP statement tells the program to halt. The END statement says where the words of the program stop. The STOP statement says where the action or execution of the program stops.
Programming is the implementation of algorithms. Algorithms tend to be expressed in an informal language, and we need to understand how to express algorithmic ideas in a "picky" language.
Informally, our first algorithm is expressed this way:
To find the square root of a number, divide the number by your guess and average.As an example of this process, if we guessed that the square root of 12 was 3, then the algorithm tells us a better guess would be (3+12/3)/2, or 3.5.
Here is an explicit plan for implementing the algorithm:
program root
a = 12.0
x = 2.0
print *, ' '
print *, 'Number whose square root is desired, A = ', a
print *, 'Initial estimate X = ', x
!
! Instead of repeating this loop 5 times, we could
! stop early if X is close enough.
!
do i = 1, 5
x = ( x + a / x ) / 2.0
print *, 'New X = ', x
end do
print *, ' '
print *, 'Correct square root is ', sqrt ( A )
stop
end program
It's not hard to find the "interesting" line that does the hard work:
X = ( X + ( A / X ) ) / 2.0.. Note how the new value of X overwrites the old one immediately. Often a beginning programmer tries to save every computed value under a different name. This is hard to program, and in complicated problems can cause you to run out of computer memory.
Notice how we arranged to carry out the process 5 times in a row, using a DO loop, which begins with the DO statement and ends with the END DO statement. The form of the DO statement
do i = 1, 5requests that the statements inside the loop be carried out five times, during which the value of I will be increased in steps of 1 from 1 to 5.
In Fortran90, comment statements begin with an exclamation mark. (Older versions used the letter C in column 1). Comments are used to discuss what is going on in the program, and often explain what algorithm is being used, or why operations were done in a particular way.
We have used lots of PRINT statements, to declare the name of the program, to describe the input, and to report the output. PRINT statements are also useful when debugging a program.
One flaw of the root program is that it doesn't have any judgment. Although we clearly can't allow a starting guess of 0, the program doesn't check for this condition. Right after the value of X is set, we really ought to do something like this:
if ( x == 0.0 ) then
print *, 'Fatal error!'
print *, ' The starting guess was X = ', x
print *, ' but this is not allowed.'
stop
end if
This is an example of an IF statement. The object inside the
parentheses is a condition to check. The operators allowed to be
used are the arithmetic comparisons:
( x == y ) ( x > y ) ( x >= y )
( x /= y ) ( x < y ) ( x <= y )
and the logical operators .NOT., .AND. and .OR.:
( x < y .and. y < z )
( .not. ( sin ( x ) == 0.0 ) )
( i == 1 .or. i == n )
The IF statement has an expanded form that includes ELSE IF and ELSE statements:
if ( x > 0 ) then
abs = x
else if ( x < 0 ) then
abs = - x
else
abs = 0
end if
where one or more ELSE IF statements can be used. The ELSE
statement, if it occurs, occurs last.
Let's show how these ideas can be used in a program. Instead of rewriting the square root example directly, we will look at a related program that carries out the bisection method to find a root of a given function.
program bisect
!
! A and B are the endpoints of an interval to be searched.
! We require that F(A) and F(B) have opposite signs.
!
a = 0.0
b = 2.0
if ( ( f(a) > 0.0 .and. f(b) > 0.0 ) .or. &
( f(a) < 0.0 .and. f(b) < 0.0 ) ) then
print *, ' '
print *, 'Fatal error!'
print *, ' The function does not change sign at the endpoints.'
stop
end if
do
c = ( a + b ) / 2.0
if ( f(c) == 0.0 ) then
exit
else if ( abs ( f(c) ) < 0.00001 ) then
exit
end if
if ( sign ( 1.0, f(c) ) == sign ( 1.0, f(a) ) ) then
a = c
else
b = c
end if
end do
print *, ' '
print *, 'Estimated root occurs at X = ', c
print *, 'Value of function is F(X) = ', f(c)
stop
end program
function f ( x )
f = cos ( x ) - x
return
end function
Look at the various IF statements and see how they are used. Note that the ABS function is the absolute value, the SIGN(A,B) function returns a value with the magnitude of A and the sign of B, and that the function f(x) is a user-defined function that is included after the end of the program.
We have used a new version of the DO statement, which is often called "DO forever". Instead of counting how many times the statements are to be repeated, we don't specify any limit explicitly. Instead, we repeat the loop until some condition is reached, and then we use the EXIT statement to leave the loop. (If we aren't careful in writing a loop like this, our program can get stuck there forever.)
Even though we have a rough idea that this program really does calculate the square root of X, we should take the time to carefully verify that this really is a Fortran program, made up of Fortran statements. And we should talk a little more carefully about what each of the lines of the program are doing there.
First of all, there is a Program statement, and a matching End statement, that tell us where the program begins and ends. There are lines beginning with ! which we know are comments. So much for the easy stuff!
There is a declaration statement that declares that I is an integer variable, and can only take on whole number values. We'll see that this is because I is used to count the number of steps we take.
There are declarations that X and Xroot are real numbers, that is, numbers that can have decimal fraction parts. Even if X was a whole number, like 7, we know that its square root could have a decimal fraction part. These declarations tell the program to be prepared to handle any number, not just integers.
The Write statements are a simple way of printing messages to the screen, and the Read is a simple way of getting information from you, the user.
The statements Continue statements are labeled statements. They don't really "do" anything, but provide a way to terminate a loop, or can be a place where we can jump with a Go To statement.
The arithmetic operators, which are used to do calculations, are:
There are some statements that check whether certain conditions are true or false, and do something based on that fact. Checking a condition has the form:
If ( X. Eq. Y ) Then
(statements to carry out if X is equal to Y)
End If
We can also stack conditions together, so that if something is true, we do one thing,otherwise, if something is true, we do another thing, as in this example:
If ( X. Lt. Y ) Then
(statements to carry out if X is less than Y)
Else If ( X .Gt. Y ) Then
(statements to carry out if X is greater than Y)
End If
Notice that in this last example, we didn't do anything
if X was equal to Y!
The Do statement begins a Do loop, that is, a group of statements that is to be repeated. The "I=1,10" in the Do statement tells us how many times we have to repeat the loop. We are to start the loop by setting the variable I to 1, then increase the value of I each time we reach the end of the loop, and keep repeating until we have completed the loop with I equal to 10. I is our loop counter, which will simply have the values 1, 2, 3, ..., 10. During each repetition we may actually use the value of I as part of the calculation if we need it. (Notice that we did this in the "hello2" example, where we printed the value of I.)
Finally, notice the indentations used. Fortran requires that most statements start in column 7 or beyond. We are free to indent those statements even further, however, to show which statements are controlled by the IF statement, and which are repeated by the DO statement, for example. This helps to make the program readable.
We've already discussed the "comment" statement, which allows you to insert remarks through your program. A Fortran program doesn't need any comments to run. Comments are for the Fortran programmer!
Programming is not an easy business. A program without comments is just a bunch of formulas. You need to write down in comments the reasons that you chose those particular formulas, what you were doing, and how you know you got the right answer. Adding comments for this purpose is called program documentation.
You might have a large beginning block of comments that describe the overall purpose of the program, and then comment individual groups of statements that carry out a step of the plan.
You should also consider using descriptive variable names when it makes sense. Your program is a script, and the variables are the "characters". If you can't remember their names, you'll never understand the plot!
When your program runs, it should also print information out. This is also a form of documentation. It might print out the program's name, the last date it was modified, and its purpose. If interactive, it might print out messages telling the user what input it is expecting. And any errors or problems that occur should result in a helpful message being printed.
The documentation for the program might also include a simple, standard problem that the program handles, and a record of the output produced by the program. This is an easy way to make sure the program isn't completely demented.
Most programs do calculations involving numbers. Usually we are working with numbers whose values are not specified when we write the program. Such quantities are called variables. We have to give variables symbolic names in a program. We use the symbolic names to set the value of variable, to alter it, or retrieve the value for use in formulas.
For instance, the following statements use the variables Sum and Average:
Program Sumup
Real Average
Real Sum
Sum = 0
Sum = Sum + 2
Sum = Sum + 16
Sum = Sum + 3
Average = Sum / 3
Write (*,*) 'The sum is ', Sum
Write (*,*) 'The average value is ', Average
Stop
End
The statements are carried out one at a time. The value of the variable Sum is altered several times. The value of Average isn't known until the last step, when the current value of Sum ,which is now 21, is divided by 3, setting Average to 7.
The names that are used for variables can be up to 6 characters long, according to the strict rules of Fortran. Most versions of Fortran will let you use longer names than that. WATFOR allows names to be up to 32 characters long. Names must start with a letter, but may include numbers. In WATFOR, a name may also include underscores or dollar signs.
When writing a program, you will find that some variables will naturally only have whole number values, associated with counting things. Other variables will have values associated with measurements, and will usually have decimal fractions. Fortran calls the first type Integer variables and the second kind Real.
Fortran programs begin with a section which lists the names of the variables to be used. The declaration statement must also declare the "type" of the variable, that is, whether it is Real or Integer. This declaration determines how much space the program will set aside to store the variable, what kind of operations will be allowed to be performed on that variable, how it will be printed out, and so on.
If you do not explicitly declare the type of a variable, a type will be assigned to it, based on the first letter of the variable's name. Variables whose names begin with I, J, K, L, M or N will be assigned Integer type, while variables with names from A to H, or O to Z will be assigned Real type. It is usually a bad idea to rely on this fact. You should always explicitly declare every variable in your program.
A type declaration statement has the form
Variable-Type Variable-Name
Variables of the same type may be declared together, separated by
commas. The following are some type declarations:
Real X
Integer Itmax, Numb, Jump
Complex Alpha
Double Precision Fred
Logical Even, Weekend, Onsale
Character*20 Myname
Declarations give information about how variables may be used. Hence, the type declarations must come before the "executable" statements, the ones that assign values to variables, read values in or write them out, or otherwise operate on the variables.
There are some other types available, some of which we will see later on. These include:Once we have declared variable names, we can begin writing statements that assign values to variables and compute formulas based on those values.
The simplest assignment statement has the form:Variable name = Constantwhere Constant is an explicit value, such as "1" or "-300.3". Of course, the constant we use should be of the proper type for the variable. For instance, if the variable were an integer, we wouldn't want to try to assign it the value "2.3". The more interesting form of the asignment statement is:
Variable name = Expression
where Expression is some formula involving constants,
variables, operators and functions. Some simple examples of
assignment statements include:
X = 1.3 Number = 17 Failing = .True. (Assigning a value to a LOGICAL variable) Name = 'Francis' (Assigning a value to a CHARACTER variable)These statements are simple to understand because they set a variable to a specific, unchanging value, called a constant. The kind of constant we are allowed to use depends, of course, on the variable we are setting. A real variable can be assigned any decimal value, but we wouldn't try to assign an integer variable the value 1.3, since it's not set up for that kind of value. Most assignment statements are more complicated. They still set the value of the variable on the left hand side of the = sign, but they do so by evaluating a formula on the right side, involving variables or constants. In order to know what the result is, you have to know the values of the variables that were used in the formula:
X = X + 1 Y = (Z + 3) * (Z - 4) Score = (Poise + Difficulty + Speed) / 3.0 Z = Sqrt(X) (This takes the square root of X)It's easy to understand what X = 1 is trying to do, but the statement X = X + 1 is really a little strange. This is not a statement in algebra. The = sign doesn't state that the left and right hand sides are equal. It says evaluate the right hand side, and store the result in the variable on the left. So now if X starts out with a value of 1, what does the statement X = X + 1 do? X + 1 has a value of 2, so we store that value in X. The right hand side of an assignment statement can sometimes get complicated. You're allowed to use parentheses and spaces to try to keep the meaning clear. For a complicated expression, you may also use temporary variables to store partial results. Compare the following versions of the same computation:
X=LENGTH**2+WIDTH*RATE*TIME/FACTOR X = LENGTH**2 + WIDTH * RATE * TIME / FACTOR X = Length**2 + (Width*Rate*Time)/Factor Area1 = Length**2 Distance = Rate*Time Area2 = Width*Distance Area2 = Area2/Factor X = Area1 + Area2
In most cases, you can mix Integer and Real quantities in formulas and assignment statements, and get the results you expect. For instance, the following are legal formulas:
Program Mix
Integer I
Integer J
Real X
Real Y
I = 2
J = 3.5
X = 2
Y = 3.5
Write (*,*) 'I=', I, ' and X=', X
I = 2.3
X = 2.3
Write (*,*) 'I=', I, ' and X=', X
I = Y
X = J
Write (*,*) 'I=', I, ' and X=', X
I = J
X = Y
Write (*,*) 'I=', I, ' and X=', X
I = 2.3 * J * Y
X = 2.3 * J * Y
Write (*,*) 'I=', I, ' and X=', X
Stop
End
We simply have to remember that, because I is an integer,
it can only hold whole number values. So when we say I = 2.3,
we take the result, 2.3, and try to store it into I. But
only the whole number part gets in, so this ends up being the
same as saying I = 2.
But Fortran handles division of integers in an unusual way, which
is NOT what you might expect.
If you are dividing two integer quantities, Fortran will chop off
the fraction part of the result, before looking to see where you
want to store the result. In many cases, this is NOT what you
want, particularly if you are assigning the result to a real value.
For instance, if you want to compute five thirds, that is,
1.6666..., here's what NOT to do, assuming that X is a
Real variable:
X = 5/3 (This will result in X=1.0!)Here, Fortran treats 2 and 3 as Integer quantities, since they don't have a decimal point, and it computes 5/3 as 1 rather than 1.6666... In order to avoid this division problem, the simplest procedure is to use a decimal point on constants. So the cure is:
X = 5.0/3.0 (This will result in X=1.6666....)The same problem happens when the quantities to be divided are variables:
I = 5 J = 3 X = I/J (This will result in X = 1.0)Here, you have to ask Fortran not to use Integer division by requesting that it temporarily treat I and J as Real values:
X = Real(I) / REAL(J) (This will result in X=1.6666....)
In order to calculate with our variables, we need operators and functions. We have already mentioned the arithmetic operators, which are available for all numeric data types:
+ Addition 2+17 X+Y A+B+C+D
- Subtraction or negation Income-Expenses A-B-C -X
* Multiplication 2*X Length*Area
/ Division I/J (X+Y)/Z
** Exponentiation 2**5 P**Q (X+1)**2
Fortran also has many "built-in" or Intrinsic functions
defined for numeric data. For example, Abs(X) represents
the absolute value of X.
In most cases, the same function name is available for use with all
numeric data types. Some of the functions which are available
include:
Abs(X) Absolute value of X.
Cos(X) The cosine of X.
Exp(X) The exponential function of X.
Int(X) Makes an Integer copy of the Real number X.
Int(1.9) is 1, for example.
Max(X1,X2) The maximum of X1 and X2.
Min(X1,X2) The minimum of X1 and X2.
Mod(X1,X2) The remainder, when X1 is divided by X2.
Mod(7,2) is 1, because 7 is odd.
Mod(14.3, 3.0) is 2.3 because 14.3 = 3*4 + 2.3.
Nint(X) Returns the nearest integer value.
Nint(1.9) is 2, for example.
Real(I) Make a Real copy of the Integer I.
Sign(X1,X2) A value having the magnitude of X1, and the sign of
X2. Sign(20.0, -7.0) is -20.0, for example.
Sin(X) The sine of X.
Sqrt(X) The square root of X.
Tan(X) The tangent of X, sine(X)/cosine(X).
If you use an intrinsic function in a program, you may declare the function by using an Intrinsic statement, as in:
Intrinsic Abs
Many programs do not bother to declare their functions in this way, and it's not strictly necessary. However, making this sort of declaration can be helpful. For one thing, this will help you avoid errors that can be caused by name conflicts. For instance, you would not want to name a variable Abs, since this would confuse the compiler. By declaring all your variables, and your functions, you can easily spot such problems.
There are also six operators which can be used to test numeric data:X .Eq. Y True if X equals Y. X .Ne. Y True if X is not equal to Y. X .Lt. Y True if X is less than Y. X .Le. Y True if X is less than or equal to Y. X .Gt. Y True if X is greater than Y. X .Ge. Y True if X is greater than or equal to Y.When we do these comparisons, we end up with a Logical result, that is, something that is true or false. We can make more complicated expressions out of such results by using the Logical operators Not, And, and Or:
.Not. Condition True if "condition" is false,
and vice versa.
Condition1 .And. Condition2 True if "condition1" and
"condition2" are both true.
Condition1 .Or. condition2 True if "condition1" is true, or
"condition2" is true, or both.
For instance, suppose we want to do something if X is in the
interval [0,1]. Then we need to say that X is greater than
or equal to 0, AND less than or equal to 1. We just use the
And operator to make sure both things are true:
If ( X .Ge. 0.0 .And. Z .Le. 1.0 ) Then
(do stuff)
End If
Do I = 1, 10
Write (*,*) 'Current estimate is ', Xroot
Xroot = (Xroot + (X/Xroot) ) / 2.0
End Do
In general, the Do statement has the form:
Do index = 1, number
statement 1
statement 2
...
last statement
End Do
The statements between the Do statement and the corresponding
End Do statement will be repeated Number
times.
Here are some examples of Do loops:
Program Count
Integer Age
Integer I
Integer J
Integer Number
Integer Sum
c
Age = 12
Do I = 1, Age
Write (*,*) 'Happy birthday to you!'
End Do
Sum = 0
Number = 0
Do J = 1, Age
Number = Number + 1
Sum = Sum + Number
End Do
Write (*,*) 'You"ve blown out ', Sum ,' candles in your life!'
Stop
End
When you use a Do statement, you have to have an "index" or
"counter". In the above example, this was a variable called
I or J. As the Do loop is carried out, the index
variable is actually set to the successive values specified in the
Do statement. In other words, the "Happy Birthday" loop
is actually equivalent to these statements:
I = 1
Write (*,*) 'Happy birthday to you!'
I = 2
Write (*,*) 'Happy birthday to you!'
...
I = Age
Write (*,*) 'Happy birthday to you!'
Because you can use the value of the loop index in formulas,
can you see how you could simplify the second loop in the
previous example, by eliminating the variable Number
and replacing it with J?
In some cases, you may want to check the value of the index
variable and use that as part of your computations. Remember,
for the following example, that the Mod function returns
the remainder of the first number when divided by the second.
Odd numbers have a remainder of 1 when divided by 2.
Do Kathy = 1, 10
If ( Mod ( Kathy, 2 ) .Eq. 0 ) Then
Write(*,*)'Kathy is even.'
Else
Write(*,*)'Kathy is odd.'
End If
End Do
Sometimes you only want to carry out an operation in certain cases. For instance, you wouldn't want to take the square root of a number Y if you find out that Y is negative. So, instead of writing
X = Sqrt ( Y )
you might prefer to write
If ( Y .Ge. 0.0 ) Then
X = Sqrt ( Y )
End If
Now the program will first check to see whether Y is nonnegative. If this is true, the statements between the words Then and End If will be carried out. Otherwise, execution will jump to the statement following End If.
Now, suppose you want to do what we normally do if Y is nonnegative, but otherwise, print out a message warning the user. Then we need the If/Then/Else statement.
If ( Y .Ge. 0.0 ) Then
X = Sqrt ( Y )
Write (*,*) 'The Square Root of Y is ', X
Else
Write (*,*) 'Your number was negative!'
Write (*,*) 'We cannot take the square root of a negative number.'
End If
The statements which are controlled by the If and Else
statements may be any number of legal statements, including more
If statements.
Now let's look at a case where we want to check two
conditions. Let's say we're given a number X, and we're
going to plot the point (X, 1/X). Naturally, we have
to check whether X is zero. However, because our vertical
axes are only marked for the range [-100, 100], we'll also
have to check whether X is so small that 1/X is too
big to plot. Here, we will use the Else If statement:
10 Continue
Write (*,*) 'Enter a value for X'
Read (*,*) X
If ( X .Eq. 0.0 ) Then
Write (*,*) 'Sorry, we can"t compute 1/X!'
Go to 10
Else If ( 1.0 / X .Gt. 100.0 .Or. 1.0 / X .Lt. -100.0 ) Then
Write (*,*) 'Sorry, 1/X is too big to plot!'
Go to 10
End If
Notice how we used the Or statement in the second check.
We can stack up Else If statements as a way to choose one
set of statements out of several groups. For instance, suppose
you were counting up the money in the cash register at the end
of the day, and you wanted to total the number of each denomination
and the total amount of money. Let's suppose you type in the
denomination of each bill to the program, except that when you
type "0", that means the program should print out the total
and stop.
program cash
Integer Fives
Integer Ones
Integer Sum
Integer Tens
Integer Value
Ones = 0
Fives = 0
Tens = 0
Sum = 0
10 Continue
Write (*,*) 'Enter denomination (1, 5 or 10), or 0 to stop'
Read (*,*) Value
If ( Value .Eq. 1 ) Then
Ones = Ones + 1
Sum = Sum + 1
Go to 10
Else If ( Value .Eq. 5 ) Then
Fives = Fives + 1
Sum = Sum + 5
Go to 10
Else If ( Value .Eq. 10 ) Then
Tens = Tens + 1
Sum = Sum + 10
Go to 10
Else
Write (*,*) 'The current total is ', sum
Stop
End If
End
We started out saying that a program operates by executing one statement, and then the next. But we saw thatDo and If statements can alter this process, by repeating some statements, or avoiding other. An even more powerful statement is available, the Go To statement which allows us to hop to any labeled statement in the program. Its form is
Go To 60
where "60" (or some other number) is the label of a Continue
statement somewhere else in the module:
60 Continue
If the Go To statement is executed, it immediately
jumps to the numbered statement, and picks up there.
One reason for using Go To statements is that Fortran does
not have a Do While statement. If you want to keep dividing
a number by 2 until the number is between 2 and 1, you'd like
to say
Do While ( X .Ge. 2.0 ) [ This is NOT legal Fortran! ]
X = X / 2.0
End Do
but you'll have to write it this way instead:
10 Continue
If ( X .Ge. 2.0 ) Then
X = X / 2.0
Go To 10
End If
Program Log2
!
! LOG2 is a program which computes the integer part of the
! logarithm base two of the number X. That is, LOG2 returns the
! number of times we can divide X by 2 before getting a value
! between 1 and 2.
!
! If X is less than 1, we return a negative value, meaning we had
! to multiply rather than divide.
!
! For example, LOG2(10)=3, because we can divide 10 by 2 just 3
! times before getting a result between 1 and 2.
!
Integer Logtwo
Real X
Write (*,*) 'This program computes the integer part of'
Write (*,*) 'the logarithm base 2 of any positive number.'
10 Continue
Write (*,*) 'Enter a number X, or 0 to stop:'
Read (*,*) X
Logtwo = 0
If ( X .Gt. 0.0 ) Then
Go To 20
End If
!
! If the user gave us a negative number, ask for a better one!
!
If ( X .Lt. 0.0 ) Then
Write (*,*) 'LOG2 - Illegal input!'
Write (*,*) 'The input value of X must be positive'
Write (*,*) 'but your value was ', X
Write (*,*) ' '
Write (*,*) 'Try again!'
Go to 10
!
! But if the user gave us 0, quit.
!
Else
Write (*,*) 'The program is stopping because you typed 0.'
Stop
End If
!
! Here is where we work on getting the logarithm of a positive number.
!
20 Continue
If ( X .Ge. 1.0 .And. X .Lt. 2.0 ) Then
Write (*,*) 'LOG2 of X is ', Logtwo
Go to 10
Else
!
! If a number is bigger than 2, divide it in half once.
!
If ( X .Ge. 2.0 ) Then
X = X / 2.0
Logtwo = Logtwo + 1
Go To 20
!
! If a number is smaller than 1, double it.
!
Else If ( X .Lt .1.0 ) Then
X = 2.0 * X
Logtwo = Logtwo - 1
Go To 20
End If
End If
End
Notice how the first Go To statement is used to "skip ahead". If X is positive, we skip over some statements that print an error message and return.
The next two Go To statements are used to jump backwards, because they are intended to be used to repeat an action until some condition is true. In this case, we want to keep dividing or multiplying X by 2, until we get a value between 1 and 2. Notice that both of these Go To statements jump to the same statement 20. You don't have to have a separate "landing place" for each Go To. The last Go To leapfrogs backward over the entire program and starts again, asking the user for a new value. Now let's look over the If/Then/Else structure of the program.The If statements that we have are "nested"; that is, inside of the If statements are more {If statements. Here's the "outer" set:
If ( X .Ge. 1.0 .And. X .Lt. 2.0 ) Then
...
Else
...
End If
In the first case, X is already in the range we want, but in
the second case, we're going to have to divide or multiply. We
don't know which until we check, and we do that with more If
statements:
If ( X .Ge. 2.0 ) Then
...
Else If ( X .Lt. 1.0 ) Then
...
End If
If X is greater than or equal to 2, it gets caught in the
first "trap", is divided by 2, and sent back to statement 20 to
be tested again to see if it's small enough yet.
If X is less than 1, it gets multiplied by 2 and sent back to
statement 20 to be tested to see if it's large enough yet.
Finally, note that there is a potential flaw in Log2.
Suppose we changed the final Write statement to
Write (*,*) 'LOG2 of ', X,' is ', LogtwoIf you give the program an input value of 10, the output will be:
LOG2 of 1.25 is 3when we would expect the program to print
LOG2 of 10 is 3
Of course, what's happening is that the Log2 program is modifying the value of X as it computes the logarithm, exactly as we told it to do. But we can easily forget that, and think that the variable called {X will keep the value we gave it initially. We could fix this problem by adding a new variable, called, say, Xcopy, which would save the input value so we can print it out later.
The Write statement prints out the value of one or more variables in the program. The Write statement has many forms, but we will concentrate on a particularly simple form, which always prints to the screen, and makes its own decisions about how many decimal places to use when printing real numbers.
You can use Write statements in your program to:
Write (*,*) X
Write (*,*) 'The cost is ', Cost,' and the rate is ', Rate
Write (*,*) 'This is a message to be printed.'
Write (*,*) 'Here is the value of Z ', Z
Write (*,*) X1, X2, X3, Y1, Y2, Y3, Z1, Z2, Z3
A Write statement of this form will print the value of the
variables. Each Write statement may try to write everything
in its list on one line. Sometimes, as in the last sample
statement above, you can try to write too much on one line, and the
result will be unsightly.
If you need more control over the form of your output, you may want
to look into the Format statement. Then you can specify the
number of decimal points used, you can force tables of numbers to
line up, and so on. We will try to avoid discussing the
Format statement in these notes; it's one of the parts of
Fortran you don't absolutely need in your first programs.
Some programs carry out a calculation from beginning to end without any help from the user. The more interesting and more useful "interactive" programs cooperate with a user. Depending on the program, the user may get to type in a single number, or perhaps lists of data to be sorted, or numbers to be added up, and so on.
In order to find out what the user wants, the program gets information that the user types on the keyboard by using the Read statement. The Read statement tells the program to expect to receive one or more items of information. In the cases we will look at, this information is typed by the user as the program waits. Advanced programmers can use alternate forms of the same Read statement, and have the program get its information from files instead. The Read statement can be very complicated, and we will try to avoid all those complications by teaching you a few very simple forms of the statement, such as:
Read (*,*) Variable
Read (*,*) X, Y, Z
A Read statement expects the user to type one or several
values, which will be assigned to the variable or variables
on the list. You should always have a Write statement just
before a Read statement, that will print out a message
telling the user that the program needs some information.
When the program expects you to give it several values, you can
type those values with spaces or commas separating them, or you can
even put them on separate lines. They must be given in the order
specified in the list.
You can Read a value into a variable whenever you like, and
as often as you like. Whatever value the variable had before is
"wiped out", and the new value is used.
Very often we have a set of data values that belong together, such as a table of temperatures, a list of grades, or the concentration of some chemical in groundwater at regularly spaced points. Now we could simply try to name each piece of information separately, say as "Grade1", "Grade2" and so on. But it will turn out that it's very hard to write good, flexible programs without having a more general way of handling lists of data.
An array allows us to group a set of data values under a single name, while retaining the ability to retrieve or change any individual value. An array is like other variables in most ways. In particular, an array has a name and a type, such as Real or Integer.
However, an array has two extra properties that give it its power: its rank and extent. The rank of an array tells us whether the data is arranged as a list, a table, or an even more complicated object. For now, let us only consider arrays of rank 1, sometimes called "vectors", "lists", or "one dimensional arrays". The extent of the array is then roughly speaking just how many entries it can hold, or in other words its size.
The extent of an array is specified in the declaration statement. Here are some simple declarations for arrays:
Real X(100)
Integer Grade(20)
Integer Kids(15)
The declaration of X tells us that it is going to be used
to store a list of up to 100 separate Real values. These
entries will be numbered from 1 to 100.
In some cases, it may not be convenient to start counting the array entries at 1. For instance, I might be sorting families by the number of children they have. I might set up an array called Kids to do this. I would want to store the number of families who have 5 children in entry 5 of Kids. But since some families have no kids, I'd need to store a value in entry 0 of Kids. I can't do that the declaration I used above, sets aside entries 1 through 15, but not 0. In order to get an entry numbered zero, I'd have to use the following declaration:
Integer Kids(0:15)
in which case I'll have entries labeled 0 through 15. Similar
declarations can be used to change the range of the entry labels
to, say, all the integers from 4 to 15.
Once we've declared an array, we need to know how to store values
in it, change them, and get them out. Well, it turns out to be
easy. All we have to do is specify which entry we want to
work with, which we do by adding an index to the name.
For instance, the seventh entry in the Grade array can be
accessed by writing Grade(7). To add 1 to it, we simply
write:
Grade(7) = Grade(7) + 1
To get the average of the first three entries in Grade, we
would type:
Average = ( Grade(1) + Grade(2) + Grade(3) ) / 3.0
Now that we have arrays, we can suddenly create hundreds or thousands of data items with a single declaration statement. But won't it take thousands of statements to set all the entries to some initial value? We will see that the Do statement will allow us to replace the ridiculous statements:
X(1) = 1.0
X(2) = 2.0
...
X(100) = 100.0
by the terse
Do I = 1, 100
X(I) = Real ( I )
End Do
Similarly, we can easily write calculations involving array elements
if we can express them using indices:
Average = 0.0
Do I = 1, N
Average = Average + X(I)
End Do
Average = Average / Real ( N )
We've agreed that one way to set all the entries of an array of 100 elements to zero would be to type 100 assigment statements, but who wants to write a program like that? The right thing to do is use the Do statement, and use the counter variable to specify which entry of the array to zero out next:
Do I = 1, 100
X(I) = 0
End Do
Now suppose that we had wanted, instead, to store the first
100 odd numbers into X? Then we'd just have to use
the counter variable I in a formula on the right hand
side as well:
Do I = 1, 100
X(I) = 2*I + 1
End Do
Similar loops can be used to copy one array into another:
Do I = 1, 100
X(I) = Y(I)
End Do
or to add a multiple of one array to another:
Do I = 1, 100
X(I) = X(I) + 2 * Y(I)
End Do
or to compute the "dot product" of two vectors, that is,
the sum of the products of pairs of corresponding entries:
Dot = 0
Do I = 1, 100
Dot = Dot + X(I) * Y(I)
End do
By the way, anything a Do statement can do can also be
done by a Go To statement. For instance, we could replace
the previous example by:
I = 1
Dot = 0
60 Continue
If ( I .Le. 100 ) Then
Dot = Dot + X(I) * Y(I)
I = I + 1
Go to 60
End If
Integer Grade(40,30)
Real Chemical(10,5)
The declaration of Chemical tells us that we are setting
aside space for a total of 50 values. The fact that we have two
numbers in the parentheses tells us that Chemical is a
"table" or ``matrix'', with rows and columns.
Entries in a two dimensional array can be changed or read the way
one dimensional arrays can be, as long as we specify both
indices. To set the value in the 6-th row and first column of
Chemical, we might type something like
Chemical(6,1) = 77.0
We used a Do loop to access all the entries of a one
dimensional array. To do the same thing with a two dimensional
array, we will need a pair of Do loops, one inside
the other. You must also use different names for the two different
Do loop indices:
Program Table
Integer I
Integer J
Integer X(3,4)
!
! Set the values of the array.
!
Do I = 1, 3
Do J = 1, 4
X(I,J) = 10*I + J
End Do
End Do
!
! Print the values of the array.
!
Do I = 1, 3
Write (*,*) (X(I,J), J = 1, 4)
End Do
Stop
End
which will result in storing the following values into X:
11 12 13 14 21 22 23 24 31 32 33 34
If you just want to read or write one or two entries of an array, or table, you can use Read and Write statements in the way we've described earlier. But let's look at what you need to do if you want to print out the whole array or table.
For a one dimensional array, here are some examples:
Write (*,*) 'The value of X(2) is ', X(2)
Write (*,*) X(1)
Write (*,*) X
Write (*,*) (X(I), I=1, 10)
The first two statements are nothing new. They simply print out a single value from the array. But the third statement is actually a request to print out every entry in the array. Since we aren't specifying how this should be done, we may not be satisfied with the way the numbers are printed out, but this is an easy way to get all the data printed quickly.
The fourth statement requests that we just print entries 1 through 10 of the array. Notice how we have to use a variable, I, as the index of X, followed by the lower and upper limits that I can take. This structure is similar to the way a Do loop is set up, and in fact, is called an implicit Do loop.
If you wanted to print all the entries of an array, but wanted just one number per line, you could use a regular Do loop like this:
Do I = 1, N
Write (*,*) X(I)
End Do
For two dimensional arrays, the situation is similar. We can print a single value, a row or column, a range of values, or the whole table, using statements like:
Write (*,*) 'The last entry of Grade is ', Grade(40,30)
Write (*,*) 'Grades for test # 3 were ', (Grade(I,3), I=1, 40)
Write (*,*) 'John"s grades were ', (Grade(17,J), J=1, 30)
Write (*,*) 'Partial grades: ', ((Grade(I,J), J=1, 5), I=1, 10 )
Write (*,*) 'Entire grade array ', Grade
It's dangerous to try to dump out a two dimensional array without
printing any other information, since it's hard to tell where one
column ends and the next begins. If we print out using a double
Do loop, we can print out the row and column information
as well, and make the data a little easier to read:
Do I = 1, 40
Do J = 1, 30
Write (*,*) I, J, Grade(I,J)
End Do
End Do
Read statements can also be used with lists and tables, and
they have to be changed in exactly the same ways that Write
statements are changed, so we won't discuss them further here!
Program Vector
!
! This program demonstrates some simple operations with vectors.
! A vector is simply a set or list of data, stored under a single
! variable name. Each item in the list can be accessed by
! specifying its "index".
!
Integer I, Jumble(10), Product, Sofar(10)
!
! Here's how a DO loop may be used to move through a vector. In
! this case, we would like to assign a value to each entry of the
! vector, and we do this by specifying a "general" entry
! Jumble(I), where I is to go from 1 to 10. Our formula, 2*I-1,
! will actually store successive odd numbers in the Jumble vector.
!
Do I = 1, 10
Jumble(I) = 2*I - 1
End Do
!
! You can print out a short array this way:
!
Write (*,*) 'Here"s what happens when we print Jumble using'
Write (*,*) 'the statement Write (*,*) Jumble:'
Write (*,*) ' '
Write (*,*) Jumble
!
! You can print out an array one entry at a time using a DO loop:
!
Write(*,*)' '
Write(*,*)'Here"s what happens when we print Jumble using'
Write(*,*)'the statement Write (*,*) Jumble(I):'
Write(*,*)' '
Do I = 1, 10
Write (*,*) Jumble(I)
End Do
!
! You can use each entry in a vector, one entry at a time, using
! a DO loop.
!
Product = 1
Do I = 1, 10
Product = Product * Jumble(I)
End Do
Write(*,*) 'The product of all the entries is ', Product
!
! What happens if we add up entries of Jumble? Let's store the
! first entry, then the sum of the first and second, then the sum
! of the first, second and third, and so on. We'll save the
! values in another vector.
!
Sum=0
Do I = 1, 10
Sum = Sum + Jumble(I)
Sofar(I) = Sum
End Do
!
! Let's print the sums out now.
!
Write(*,*)' '
Do I = 1, 10
Write(*,*)'The sum of the first ',I,' entries is ', Sofar(I)
End Do
Stop
End
The Parameter statement is used to define the value of a constant. The only difference between a constant and a variable is, naturally, that a constant is not allowed to be changed. To define a constant, you should simply follow the declaration of the type of the variable by a statement that assigns it its permanent value, as in:
Real Pi
Parameter ( Pi = 3.14159265 )
You can then use Pi just like a variable, as long as you
don't try to change its value.
Actually, the most useful feature of the Parameter statement is for
declaring the size of an array. Fortran requires you to specify
the size of an array as a constant, but in many cases, it would
be convenient to declare it as a variable, or at least as a
symbolic constant. That way, it's much easier to change the
declaration if I want to work with larger arrays, for instance.
Also, many times, there are several arrays which all share the
same size. Storing that size as a constant makes it possible
to redimension all the arrays with a single change. For instance,
here is part of the declarations of arrays in a program that is
going to set up and solve a linear system of maximum size 10:
Real A(10,10)
Integer Pivot(10)
Real Rhs(10)
Real Sol(10)
If I want to increase the problem size to 20, I have to change
each 10 separately. If I use a Parameter statement, I can
do this in a much cleaner way:
Integer Maxn
Parameter (Maxn=10)
Real A(Maxn,Maxn)
Integer Pivot(Maxn)
Real Rhs(Maxn)
Real Sol(Maxn)
A program that is large or complicated can be simplified by using a subroutines. A subroutine is a "mini-program", which is used by the main program to carry out some specific part of the overall task. There is an almost identical concept, called a Function, which has a few special features not used in subroutines. We will discuss them shortly.
The most common reasons for using subroutines are:
Program Topcat
Write (*,*) 'This is Topcat speaking! Is Garfield home?'
Call Garfield
Write (*,*) 'This is Topcat again. Is Underdog around?'
Call Underdog
Write (*,*) 'This is Topcat again. I"m ready for a nap!'
Stop
End
Subroutine Garfield
Write (*,*) 'Garfield speaking. I"m in charge now!'
Call Underdog
Write (*,*) 'This is Garfield, and I"m stepping down now!'
Return
End
Subroutine Underdog
Write (*,*) 'Underdog here! Leave me alone!'
Return
End
Execution of this program begins in the main Topcat program.
The main program carries out its first task, the Write
statement, and then calls Garfield. Topcat must now
wait until Garfield is done before proceeding to the next
statement. Garfield now has control. It prints out a
message and calls Underdog. Again, Garfield must wait
while Underdog does its work, which is simply to print out
a complaint. Then control passes back to Garfield, and
shortly thereafter to Topcat, which has been waiting all
this time! Topcat then prints another message, and calls
Underdog directly, and then finishes up.
This process, in which one portion of a program pauses and lets
another subroutine take over for a while, is called the transfer of
control, or transfer of execution. Using subroutines is another
way to interrupt the normal transfer of control, which is from one
statement to the next consecutive statement.
In the Topcat example, there was no "communication" between the main program and the subroutines. To get a real benefit from subroutines, we will need to be able to set up some sort of communication between the calling program and the subroutine. This will allow the program to "send" the subroutine numbers or other data, and the subroutine to "return" the results of its operations on that data.
The method that is used to pass information to subroutines involves what is called an "argument list". An argument list is a list of variable names, surrounded by parentheses. When a program calls a subroutine, the names used in the argument list of the Call statement will specify what information the program wants to send to the subroutine. A Call statement with arguments looks something like this:
Call Fred(X, Y, Bananas)
Here X, Y and Bananas are names of variables used
in the main program, whose values are to be sent to subroutine
Fred.
The subroutine also has an argument list, and this list specifies
the names that the subroutine will use to refer to the same
variables. The subroutine lists its arguments using a
Subroutine statement, such as:
Subroutine Fred(A, B, Apples)
Here A, B, and Apples are names of variables
which are used in subroutine Fred.
In order for the main program to use a subroutine, it must set up a
Call statement that matches the Subroutine statement.
The number and type of the variables must be the same, but there is
no need to use the same names. In effect, variables may get
temporarily "renamed" when they are sent to the subroutine.
Program Bill
Real Tea, Coffee, Water, Chicken
Real Beef, Cake, Pie, Icecream
Real Price1, Price2
!
! Set the prices for food.
!
Tea = .50
Coffee = .45
Water = 0.0
Chicken = 1.75
Beef = 2.50
Cake = 1.50
Pie = 1.25
Icecream = 1.75
Call Waiter ( Tea, Chicken, Pie, Price1 )
Write (*,*) 'First order will cost ', Price1
Call Waiter ( Water, Beef, Cake, Price2 )
Write (*,*) 'Second order will cost ', Price2
Stop
End
Subroutine Waiter ( Item1, Item2, Item3, Cost )
Real Item1, Item2, Item3
Real Total, Tip, Cost
Total = Item1 + Item2 + Item3
Tip = 0.15 * Total
Cost = Total + Tip
Return
End
The Waiter subroutine looks like a program, but we can tell that it is not a program. There are the obvious differences of the Subroutine and Return statements, and the variables Item1, Item2, and Item3 are not given initial values in Waiter itself. Moreover, Waiter never prints out any results, which means it would be a pretty useless program by itself!
So Waiter is just a portion of a computation. The initialization of the data must be done elsewhere, and the results must be used or printed elsewhere. The program that calls Waiter is responsible for defining the data, and using the results.
Now notice how the main program, Bill, calls Waiter two times. In both cases, the names that Bill uses for the variables are not the same as what Waiter calls them! However, we can understand what's going on, as long as we make the following correspondence:
What Bill calls: has the following name in Waiter
Tea Water Item1
Chicken Beef Item2
Pie Cake Item3
Price1 Price2 Cost
Notice that the names may be different, but that the corresponding variables Tea and Water and Item1 have the same type, Real in this case. Subroutine argument names don't matter, but the position in the argument list, and their values and types do matter.
Notice also that we have established two-way communication here. The first three arguments are "input" quantities to Waiter, but the final one is "output", that is, Waiter sets the value of Cost, or modifies it. So it really is possible to send raw data to a subroutine and have the finished results returned. Finally, note that a subroutine like Waiter is in many respects a "closed off" world. The only communication with the outside world is done through the Subroutine statement. But variables like Tip and Total, which do not appear in the argument list of the Subroutine statement, are completely private. The Bill program cannot alter them, or print them out, or use them in a formula.Statement labels are completely private to a subroutine. The main program can have a statement labeled 10, and Waiter can have a different statement labeled 10, without any conflict. For the same reason, if the main program has a statement labeled 10, the Waiter program can not jump there with a Go To statement.
The subroutine requires a declaration for the variable, just as for any variable. Since it's an array, we'll also need to use some parentheses in the declaration, with a size indicator inside. But exactly how we indicate the size of the array depends on how the subroutine is to be used.
Subroutine Barney(Vec, Table)
Real Vec(10), Table(50,10)
Subroutine Fred(Vec, Nvec, Table, Nrow, Ncol)
Integer Nvec
Real Vec(Nvec)
Integer Nrow, Ncol
Real Table(Nrow, Ncol)
Using the second option will make our calls to subroutines more lengthy, and it will introduce another opportunity for mistake, but it allows us to write a very general purpose subroutine! For instance, it means we can write a subroutine that will add up the entries of any Real vector:
Subroutine Vsum ( N, X, Sum )
Integer N
Integer I
Real Sum
Real X(N)
Sum = 0.0
Do I = 1, N
Sum = Sum + X(I)
End Do
Return
End
Program Vmax
!
! This program demonstrates how vectors of different length may be
! sent to the same subroutine. The subroutine simply needs to
! have the additional information about the length of the vector.
!
Integer I
Integer Imax
Integer N1
Integer N2
Real Value
Real Vec1(5)
Real Vec2(10)
Write (*,*) ' '
Write (*,*) 'Here is vector 1:'
Write (*,*) ' '
N1 = 5
Do I = 1, 5
Vec1(I) = I*I - 7*I + 20
Write (*,*) I, Vec1(I)
End Do
Call Vecmax ( Vec1, N1, Imax, Value )
Write (*,*) 'Maximum is ', Value, ' in entry ', Imax
Write (*,*) ' '
Write (*,*) 'Here is vector 2:'
Write (*,*) ' '
N2 = 8
Do I = 1, 8
Vec2(I) = -I*I + 5*I + 24
Write (*,*) I, Vec2(I)
End Do
Call Vecmax ( Vec2, N2, Imax, Value )
Write (*,*) 'Maximum is ', Value,' in entry ', Imax
Stop
End
Subroutine Vecmax ( Vec, N, Imax, Vmax )
!
! Vecmax accepts a vector Vec of N items. It finds Imax, the
! location of the largest item, and Vmax, the value of the largest item.
!
Integer N
Integer I
Integer Imax
Real Vec(N)
Real Vmax
Imax = 1
Vmax = Vec(1)
Do I = 2, N
If ( Vec(I) .Gt. Vmax ) Then
Imax = I
Vmax = Vec(I)
End If
End Do
Return
End
A function is typically a formula that returns a predictable result based on input it receives. We have already seen that Fortran has functions, since we've talked about the "intrinsic" mathematical functions like Abs and Sqrt.
If you have any formulas that are used repeatedly in your computation, you may find it worthwhile to try framing those formulas in terms of a Fortran Function. A Function that is written by the programmer is called an external function, and the keyword External should be used to declare the Function in any module where it is used.
What are the differences between a Function and a Subroutine? Let's first look at the differences from the inside, that is, the differences in how a Function of Subroutine is written. Suppose we want to compute the area of a box using its length and width. We could write either:
Subroutine Box ( Length, Width, Area )
Real Area
Real Length
Real Width
Area = Length * Width
Return
End
or:
Function Area ( Length, Width )
Real Area
Real Length
Real Width
Area = Length * Width
Return
End
The most surprising thing is how similar the two codes are! But let's look at the differences, which all have to do with the way the name Area is used. In the subroutine, Area is just another variable, which happens to be an output quantity. The subroutine has a name, "Box", which was just made up, and isn't used anywhere within the body of the subroutine.
On the other hand, the function is named Area, declares the type of Area, and computes Area based on its input quantities. Notice that Area is NOT a variable in the argument list, where you would normally expect it to be for a subroutine.
This is the unique feature of a Function: There is a special variable, whose value is to be computed by the Function, and whose name is the name of the Function. Instead of passing a list of inputs and outputs back and forth, the Function receives inputs in the argument list, and returns a single output, as the value of its name.
Now let's compare how functions and subroutines appear from the outside, that is, when a program uses them. We can ask them both to compute the area of a box, using the following code:
Program Funsub
Real Area
Real Area1
Real Area2
Real Length
Real Width
External Area
Length = 2.0
Width = 5.3
Write (*,*) 'Length=', Length, ' Width=', Width
Call Box ( Length, Width, Area1 )
Write (*,*) 'Box computes the area as ', Area1
Area2 = Area ( Length, Width )
Write (*,*) 'Area computes the area as ', Area2
Stop
End
We see that in both cases, we pass information to the function or subroutine in the same way, through an argument list. But we get information out of the function in a new way, by using the name of the function as though it were a variable, whose value could be used in formulas or assigned to another variable.
The game of Life is played on an imaginary rectangular checkerboard of some given size. The game begins with some squares of the checkerboard containing a single marker, and the other squares empty. At each step of the game, we replace the current pattern on the checkerboard by a new pattern, adding or removing markers according to a set of simple rules. Each box or cell can be empty, or have just one marker in it. The rules tell us which markers to add or delete, and we say a marker has died, or a new one has been born.
The rules for making the next pattern from the current one are: Look at each cell individually. Examine the cell's eight immediate neighbors:
NW | N | NE
---+---+---
W | | E
---+---+---
SW | S | SE
+---+---+---+---+
| X | | X | |
+---+---+---+---+
| | X | | |
+---+---+---+---+ The starting pattern
| | | | X |
+---+---+---+---+
| | | | X |
+---+---+---+---+
+---+---+---+---+
| | X | | |
+---+---+---+---+
| | X | X | |
+---+---+---+---+ Step 1. Only one marker survives from the
| | | X | | original pattern.
+---+---+---+---+
| | | | |
+---+---+---+---+
+---+---+---+---+
| | X | X | |
+---+---+---+---+
| | X | X | |
+---+---+---+---+ Step 2. No markers die. Two are born.
| | X | X | |
+---+---+---+---+
| | | | |
+---+---+---+---+
Program Life
!
! On a 10 by 10 board, place 0's and 1's randomly, leaving the
! border 0. 1 is a live cell, 0 a dead cell. On each step, a
! living cell "dies" if it has 0, or 1, or more than 3 living
! cell neighbors. On each step, an dead cell comes alive if it
! has exactly 3 living cell neighbors.
!
Integer Nrow
Integer Ncol
Parameter ( Nrow = 10 )
Parameter ( Ncol = 10 )
Integer Cell(Nrow,Ncol)
Integer I
Integer Istep
Integer Old(Nrow,Ncol)
Call Init ( Cell, Old, Nrow, Ncol )
Istep = 0
Call Display ( Cell, Istep, Nrow, Ncol )
Do I = 1, 5
Call Compute ( Cell, Old, Nrow, Ncol )
Istep = I
Call Display ( Cell, Istep, Nrow, Ncol )
End Do
Stop
End
Subroutine Init ( Cell, Old, Nrow, Ncol )
!
! Initialize the values in Cell to random 0 and 1 values.
! Make sure the boundary is 0.
!
Integer Nrow
Integer Ncol
Integer Cell(Nrow,Ncol)
Integer I
Integer Iseed
Integer J
Integer Old(Nrow,Ncol)
Real Random
External Random
Iseed = 1993
Do I = 1, Nrow
Do J = 1, Ncol
If ( I .Eq. 1 .Or. I .Eq. Nrow .Or.
& J .Eq. 1 .Or. J .Eq .Ncol ) Then
Cell(I,J) = 0
Else
Cell(I,J) = Nint ( Random ( Iseed ) )
End If
Old(I,J) = Cell(I,J)
End Do
End Do
Return
End
Subroutine Compute ( Cell, Old, Nrow, Ncol )
!
! Update the cell information for the next step.
!
Integer Nrow
Integer Ncol
Integer Cell(Nrow,Ncol)
Integer I
Integer J
Integer Newval
Integer Old(Nrow,Ncol)
Do I = 1, Nrow
Do J = 1, Ncol
Old(I,J) = Cell(I,J)
End Do
End Do
Do I = 1, Nrow
Do J = 1, Ncol
If ( I .Eq. 1 .Or. I .Eq. Nrow .Or.
& J .Eq. 1 .Or. J .Eq. Ncol ) Then
Newval=0
Else
Nabors = Old(I-1,J-1)+Old(I-1,J ) + Old(I-1,J+1) +
& Old(I, J-1) + Old(I, J+1) +
& Old(I+1,J-1)+Old(I+1,J ) + Old(I+1,J+1)
If ( Nabors .Le. 1 ) Then
Newval = 0
Else If ( Nabors .Eq. 2 ) Then
Newval = Old(I,J)
Else If ( Nabors .Eq. 3 ) Then
Newval = 1
Else
Newval = 0
End If
End If
Cell(I,J) = Newval
End Do
End Do
Return
End
Subroutine Display ( Cell, Istep, Nrow, Ncol )
!
! Display prints out the cell information.
!
Integer Nrow
Integer Ncol
Integer Cell(Nrow,Ncol)
Integer I
Integer Istep
Write (*,*) ' '
Write (*,*) 'Step ',Istep
Write (*,*) ' '
Do I = 1, Nrow
Write (*,'(10i1)') ( Cell(I,J), J = 1, Ncol )
End Do
Return
End
Function Random ( Iseed )
!
! Random returns a "random" value. Before the first call to
! Random, set Iseed to some large, nonzero value.
!
Integer Iseed
Real Random
Intrinsic Real
Iseed = Iseed * 125
Iseed = Iseed - ( Iseed / 2796203 ) * 2796203
Random = Real ( Iseed ) / 2796203.0
Return
End
Notice that the actual main program is very skimpy! It simply declares some arrays and variables, and "orchestrates" the computation by calling subroutines.
Notice the "travels" of the Cell array. It is declared in the main program, initialized in Init, updated by Compute, and printed by Display. It really gets around! And each of the subroutines can be defined by what it does to the Cell array.
For the Cell array, we chose to represent the absence of a marker by the value 0, and the presence of a marker by the value 1. This made it very easy to compute the number of "living" neighbors of a given cell by simply adding up their values. If we had chosen different values (say 1 for no marker, and 2 for a marker), we would have had a more complicated time figuring out the number of neighbors. To keep things simple, we also added a boundary layer of cells that were always zero, and then we only updated the 8 by 8 inner block of cells. This allowed us to use the same simple formula for the number of neighbors in all cases. When you add the neighbors up in your head, you automatically adjust for the special cases that occur along the boundary. But to keep the program simple, we had to make the data a little more complicated. Note also that we had to have an Old array, to store the current value of the pattern. This is because the Game of Life needs a copy of all the old values around until it is completely done determining the new values.Finally, the Write statement in subroutine Display
Write (*,'(10i1)') (Cell(I,J), J=1, Ncol)
is an example of a formatted Write statement. We are
requesting that the data be printed out as rows of Integer
values, each taking up just one space, and we are asking that
up to 10 values be printed on one line.
Normally, we would use the simpler line:
Write (*,*) (Cell(I,J), J=1, Ncol)
but then the information is printed out in a way that's hard to
read. If you want to have more control over your Write
statements, then it's time for you to look up the Format
statement!
You can return to the HTML web page.