A look back: FORTRAN 77
These days, a lot of programming languages look alike. You don’t have to be a Rust programmer to read a Rust program and get a general idea about what it does. If you know a little C, or C++, or Java, or C#, or Go, … or any of a number of other “C derived” programming languages, you can probably read other “C derived” languages. These C-family programming languages all use the semicolon (;
) to end statements, parentheses (()
) to isolate comparisons, curly braces ({}
) for program blocks, standard symbols for comparison (such as <
for less than), and other language features.
But programming languages haven’t always looked like this.
FORTRAN 77 provides an interesting view into programming languages of the past. Let’s take a step back in time to look at how programmers created new programs using punched cards with FORTRAN 77. This isn’t meant to be a deep dive, but a primer that provides an overview of FORTRAN 77, with a brief introduction for how to write your first programs in FORTRAN 77.
To follow along, you’ll need to install a FORTRAN compiler. The GNU Compiler Collection includes a very nice FORTRAN compiler, which you can install using the gcc-gfortran package:
$ sudo dnf install gcc-gfortran
I’ll use old-style FORTRAN 77, which the GNU compiler may complain about. You can avoid these messages by passing the -std=legacy
option to the compiler. On my system, I created this 2-line shell script called f77
that does it for me:
#!/bin/bash
gfortran -std=legacy "$@"
A brief history
In the now-distant origins of computing, programming languages were quite different. One venerable programming language – in fact, one of the earliest compiled languages – was FORTRAN, an acronym for FORmula TRANslation. FORTRAN looked and acted differently as a programming language because of the technology era in which it was born. FORTRAN programs were written on punched cards – literally, cards made from heavy paper and punched with holes, where each combination of holes represented a different character.
The first version of the FORTRAN programming language was conceived in the 1950s. FORTRAN was updated over time, including FORTRAN-II and FORTRAN-III in the late 1950s, FORTRAN-IV in the early 1960s, FORTRAN 66 in 1966, and FORTRAN 77 in 1977. Later versions of the language changed the spelling to Fortran, starting with Fortran 90 in 1990; the most recent Fortran standard is Fortran 2023. However, a modern Fortran compiler remains backwards compatible with earlier versions of the language, so you can still compile a FORTRAN 77 program using a more recent Fortran compiler.
Throughout its development, FORTRAN was popular with scientists and engineers because the language made it easy to translate formulas and calculations into computer programs. For example, I learned FORTRAN 77 as an undergraduate physics student in the early 1990s; we used FORTRAN 77 because while the Fortran 90 standard was expected in 1990, it didn’t get released until 1991, and compilers weren’t available for some time after that.
Uppercase and 80 columns
The IBM Model 026 punched card was immensely popular since its introduction in July 1949. It was used pretty widely in businesses for data processing. And even though the character set was limited, it remained a popular medium for programming languages (like FORTRAN) into the early 1970s.
The Model 026 had at least two common character sets: one was for commercial applications, the other was for FORTRAN programming. The main difference seems to be plus (+
) in FORTRAN (&
in commercial) and equals (=
) and single quote ('
) and parentheses (()
). I’ll work with the FORTRAN character set here.
Each character on a card was represented by a series of holes punched into the card, arranged in a column. By changing the order of these holes, you could define different characters. However, cards had a physical limit: if you punched too many holes too closely together, the card might easily tear. As a result, the Model 026 punched card defined the character set so that for any single letter, you didn’t have two holes next to each other.
As a result, FORTRAN punched cards supported a character set containing only the numbers 0 to 9, uppercase letters A to Z, and a short list of special characters: space, equals (=
), plus (+
), minus (-
), star (*
), slash (/
), comma (,
), period (.
), apostrophe ('
), dollar ($
), colon (:
), and left and right parentheses (()
).
Cards also had limited space, and FORTRAN had this same limitation. Of the 80 columns on a punched card, the last 8 columns were reserved for card sorting. If you dropped a stack of punched cards on the floor, you could load the cards into a card sorting machine, which would put the cards back into some semblance of order. New cards might come with the last 8 columns pre-punched with an increasing series of numbers, providing a default ordering.
The first 5 columns were reserved for optional statement labels, or a comment marker if column 1 contained either C
or *
. That leaves 80 – 8 – 5 = 67 columns on each card, which isn’t a lot of space to write a program instruction, especially a nontrivial one for a complicated program. To work around this limitation, FORTRAN reserved column 6 for a continuation marker, which was usually a number or a special symbol like +
. The remaining 66 columns were for actual program statements. This allowed programmers to combine several cards to make one long FORTRAN statement.
The columns will make more sense as we learn more about FORTRAN.
Hello world
To start a FORTRAN 77 program, use the PROGRAM
keyword, followed by the name of the program. The program name can be almost anything, as long as it isn’t a keyword. However, if you call the program COUNT
, then you can’t have a variable in the program called COUNT
. To avoid name collisions, I sometimes write my program name with a few zeroes tacked on the end.
To end a program definition, use the END
statement.
You write your program between the PROGRAM
and END
statements. Let’s get started by writing a simple “Hello world” program. This requires just three statements:
PROGRAM HELLO
PRINT *, 'HELLO'
END
This uses the PRINT
statement, which is a reserved FORTRAN statement. The *
indicates a format statement to use, but we don’t need to get into formatted output in this primer, so we’ll just use *
to tell FORTRAN to use some default formatting.
Also notice how FORTRAN defines a string: it uses single quotes ('
) around the word HELLO
, instead of double quotes. The double quote ("
) didn’t exist in the Model 026 punched card, so it wasn’t available in the original FORTRAN character set.
Save this as hello.f
and compile it:
$ f77 -o hello hello.f
$ ./hello
HELLO
Variables and values
Programs are more useful if they can work with values in memory. These are called variables, and FORTRAN 77 provides several basic types, including INTEGER
for counting numbers, REAL
for floating point values, DOUBLE PRECISION
for larger values, COMPLEX
for numbers that contain an imaginary component like 1+2i, and CHARACTER
for letter values. Let’s start by examining the REAL
variable type with this program:
PROGRAM REAL000
REAL X,Y
X=1.0
Y=2.0
PRINT *, X
PRINT *, Y
END
I’ve named the program REAL000
to “pad out” the name so it won’t collide with the REAL
keyword.
While FORTRAN 77 supports an IMPLICIT
rule where you might assume variables that start with I
through N
are integer values, and all other letters imply floating point variables, you can avoid a lot of programming headaches by defining the variables you need to use the data type they require. To define two floating point variables X
and Y
, I used the REAL
keyword, then the names of the variables, separated by a comma.
To assign values to these variables, make sure your number has a decimal point in it. The decimal point implies a floating point number; no decimal point suggests an integer value, and you cannot assign an integer value to a REAL
variable. In this sample program, I was very explicit by writing 1.0
and 2.0
.
$ f77 -o real real.f
$ ./real
1.00000000
2.00000000
You might also notice that my program doesn’t consistently use spaces everywhere. An interesting side effect of the limited space on a punched card is that FORTRAN 77 programs don’t need spaces between keywords, variable names, and symbols. This feature allowed programmers to write long statements on a single card. For example, let’s write essentially the same program using DOUBLE PRECISION
variables, but without the spaces:
PROGRAMDBL000
DOUBLEPRECISIONX,Y
X=1.0
Y=2.0
PRINT*,X
PRINT*,Y
END
That may be hard for a human to read, but the FORTRAN compiler will recognize PROGRAM
as a keyword, and DBL000
as the name of the program. Similarly, the compiler will interpret DOUBLEPRECISION
as DOUBLE PRECISION
, and will understand that X
is the name of the first variable.
$ f77 -o dbl dbl.f
$ ./dbl
1.0000000000000000
2.0000000000000000
Conditional evaluation
Like other programming languages, FORTRAN supports conditional evaluation using an IF
statement. However, things get a little weird here. You might be used to testing values like if (x<2)
or if (a>b)
, but the FORTRAN character set doesn’t include “less than” or “greater than” characters. Instead, FORTRAN uses abbreviations for each of the comparisons:
Comp. | Meaning |
---|---|
.LT. | Less than |
.LE. | Less than or equal |
.GT. | Greater than |
.GE. | Greater than or equal |
.EQ. | Equal to |
.NE. | Not equal to |
The dots around each abbreviation are part of the keyword, so don’t forget to include them. Let’s test each of these using a series of 1-line IF
statements:
PROGRAM IF000
IF (1.LT.2) PRINT *, '1 IS LESS THAN 2'
IF (2.GT.1) PRINT *, '2 IS GREATER THAN 1'
IF (3.EQ.3) PRINT *, '3 IS EQUAL TO 3'
IF (1.GE.0) PRINT *, '1 IS GREATER OR EQUAL TO 0'
IF (1.GE.1) PRINT *, '1 IS GREATER OR EQUAL TO 1'
IF (3.LE.2) PRINT *, 'SHOULD NOT SEE THIS'
IF (3.LE.3) PRINT *, '3 IS LESS THAN OR EQUAL TO 3'
END
All of these statements should generate output, except one. The exception is 3.LE.2
, because 3 is not less than or equal to 2. If you compile and run the sample program, you’ll see the other statements print a message:
$ f77 -o if if.f
$ ./if
1 IS LESS THAN 2
2 IS GREATER THAN 1
3 IS EQUAL TO 3
1 IS GREATER OR EQUAL TO 0
1 IS GREATER OR EQUAL TO 1
3 IS LESS THAN OR EQUAL TO 3
Logical expressions and tests evaluate to either .TRUE.
or .FALSE.
and can be combined with several logical operators like these:
Op. | Meaning |
---|---|
.NOT. | Opposite value |
.AND. | Both are true |
.OR. | Either is true |
We can see how FORTRAN uses these operators by writing a sample program:
PROGRAM AND000
IF (.NOT. (1.LT.2)) PRINT *, 'SHOULD NOT SEE THIS'
IF ( (1.LT.2) .OR. (3.LT.2) ) PRINT *, '1 LT 2'
IF ( (1.LT.2) .AND. (1.LT.3) ) PRINT *, '1 LT 2 AND LT 3'
END
The first statement shouldn’t generate any output, because 1 is less than 2, but this test is negated, so the .TRUE.
value becomes a .FALSE.
instead. However, the other two tests should print a message:
$ f77 -o and and.f
$ ./and
1 LT 2
1 LT 2 AND LT 3
Early versions of FORTRAN supported only this 1-line IF
statement. However, later versions of FORTRAN, such as FORTRAN 77, allowed IF
statements to start a block of conditional execution. Use IF(
…)THEN
to start a block, and end it with END IF
:
PROGRAM IFTHEN0
IF ( (1.LT.2) .AND. (1.LT.3) ) THEN
PRINT *, '1 IS LESS THAN 2'
PRINT *, '1 IS LESS THAN 3'
END IF
END
The extra spaces are for clarity – but “indenting” statements was not often done in early FORTRAN programs because of the limited space on each punched card. Remember, a card could only hold 66 columns of source code, so space was at a premium. It might be more common to see this program written without spaces in FORTRAN 77:
PROGRAM IFTHEN0
IF((1.LT.2).AND.(1.LT.3))THEN
PRINT*,'1 IS LESS THAN 2'
PRINT*,'1 IS LESS THAN 3'
ENDIF
END
Either way, the program generates the same output, because 1 is less than both 2 and 3:
$ f77 -o if-then if-then.f
$ ./if-then
1 IS LESS THAN 2
1 IS LESS THAN 3
You can also use an ELSE
condition to do something else if the test fails:
PROGRAM ELSE000
IF (1.GT.2) THEN
PRINT *, 'NOT TRUE'
ELSE
PRINT *, '1 IS NOT GREATER THAN 2'
ENDIF
END
The first test should never succeed; 1 is definitely less than 2, not greater than 2. So only the ELSE
block should execute:
$ f77 -o else else.f
$ ./else
1 IS NOT GREATER THAN 2
For other tests in the same IF
block, use the ELSE IF
statement. This introduces another test where you can examine some other value. If you use ELSE
, you should put it last:
PROGRAM ELSEIF00
IF (1.GT.2) THEN
PRINT *, 'NOT TRUE'
ELSE IF (1.LT.2) THEN
PRINT *, '1 IS LESS THAN 2'
ELSE
PRINT *, 'NOT TRUE'
ENDIF
END
In this program, only the ELSE IF
should execute, because 1 is less than 2:
$ f77 -o else-if else-if.f
$ ./else-if
1 IS LESS THAN 2
Labels, loops, and jumps
A powerful feature of any programming language is the loop, where the program iterates a variable over a set of values while it performs tasks. In FORTRAN, that’s the DO
loop. To create a DO
loop, you need to define the start and end values, and an optional step value.
In other programming languages, you might use curly braces to define the block of statements to execute in the loop. But FORTRAN 77 doesn’t have this concept. Instead, the DO
loop also requires a label to define the last program statement in the loop. Labels are always numbers, and must be written in the first 5 columns of a line.
Let’s define a simple loop so you can see what I mean. This loop prints a list of numbers from 1 to 5:
PROGRAM DOLOOP
INTEGER I
DO 10 I = 1, 5
10 PRINT *, I
END
If we compile and run the program, we will see the loop starts at 1 and ends at 5. Because the program did not define a step value, the sequence counts up by 1:
$ f77 -o do do.f
$ ./do
1
2
3
4
5
To see the step value in action, let’s modify the program to print the numbers from 1 to 9, but only every other number:
PROGRAM DOLOOP
INTEGER I
DO 10 I = 1,10,2
10 PRINT *, I
END
This version of the program starts at 1 but ends at 10, incrementing the I
variable by 2 after each loop. But because it can only print odd numbers, the loop actually ends at 9:
$ f77 -o do do.f
$ ./do
1
3
5
7
9
The pseudocode for this FORTRAN 77 loop might look like this, if written in a C-like language:
for (i = 1; i < 10; i += 2) {
print i;
}
While ending the DO
loop on a program instruction is valid in FORTRAN 77, later editions of the language deprecated it because it can lead to subtle bugs if you’re not careful. Starting with FORTRAN 77, programmers were encouraged to make the last statement in the loop a CONTINUE
instruction – this is effectively a “do nothing” instruction, but makes for more readable code:
$ cat do.f
PROGRAM DOLOOP
INTEGER I
DO 10 I = 1,10,2
PRINT *, I
10 CONTINUE
END
FORTRAN 77 only supports the DO
loop, which is effectively a for loop. If you need another kind of loop, such as a while loop, you need to “build” it yourself using a GO TO
instruction. The GO TO
is a jump instruction that picks up program execution at a specific line label. For example, we can rewrite the DO
loop using an IF
and a GO TO
instruction, like this:
PROGRAM IFGOTO
INTEGER I
I = 1
10 PRINT *, I
I = I + 1
IF (I.LE.5) GOTO 10
END
This program is a little longer than the DO
loop, but it’s still short enough to read: The program initializes the I
variable to 1, then prints it. After incrementing the variable by 1, it checks if the value is less than or equal to 5. If it is, then it restarts the loop by jumping to the PRINT
instruction. Otherwise, execution passes to the next instruction, which ends the program.
$ f77 -o if-goto if-goto.f
$ ./if-goto
1
2
3
4
5
While modern programmers tend to shy away from jump statements, the GO TO
can be very useful in FORTRAN 77 programs. Let’s write a simple but nontrivial program to demonstrate how to use the GO TO
in a loop to simulate an object falling under gravity. Physics was a common application for FORTRAN programs, so it seems apt to demonstrate the GO TO
with a physics equation of motion.
For any moving object, you can track it’s position over time using the equation:
y(t) = y0 + v0t + (1/2)at2
PROGRAM FALL
REAL Y,V0,T,A
C INITIALIZE VARIABLES
Y = 10.0
V0 = 0.0
A = 9.8
T = 0.0
C SIMULATE A FALLING OBJECT UNDER GRAVITY
10 Y = Y + V0 * T - .5 * A * T * T
PRINT *, T,Y
T = T + .1
IF (Y.GT.0.0) GOTO 10
END
I’ve added a few comment lines so it’s easier to follow along. Remember that a comment line starts with C
or *
in the first column. With these comments, it’s easier to see that the program first initializes a set of variables (height Y
, initial velocity V0
, acceleration A
, and time T
) then enters a loop to iterate time by 0.1 seconds until the object’s height is zero. At each step in the loop, the program prints the time and the height:
$ f77 -o fall fall.f
$ ./fall
0.00000000 10.0000000
0.100000001 9.95100021
0.200000003 9.75500011
0.300000012 9.31400013
0.400000006 8.52999973
0.500000000 7.30499983
0.600000024 5.54099989
0.700000048 3.13999963
0.800000072 3.99899483E-03
0.900000095 -3.96500182
This simulation shows that an object can fall 10 meters in just over 0.8 seconds. At that time, the height is 0.00399, which is almost zero. The next iteration at 0.9 seconds sees the object at almost 4 meters underground.
We can write a similar program to simulate a trajectory in two dimensions, also using the physics equation of motion. We’ll use the assumption that there’s no drag on the object, so the object’s x velocity doesn’t have a deceleration. That simplifies the x position as:
x(t) = x0 + v0t
PROGRAM TRAJ
REAL X,X0,Y,Y0,T,A,V0,ANGLE
REAL PI
C INITIALIZE VARIABLES
PI = 3.141459
Y0 = 0.0
X0 = 0.0
T = 0.1
A = 9.8
V0 = 5.0
ANGLE = 45.0
C SIMULATE A TRAJECTORY
10 Y = Y0 + V0*SIN(ANGLE*PI/180.0) * T - .5 * A * T * T
X = X0 + V0*COS(ANGLE*PI/180.0) * T
PRINT *, T,X,Y
T = T + .1
IF (Y.GT.0.0) GOTO 10
END
Because a trajectory requires an angle, this program uses the built-in trigonometry functions for sin and cos, converting the angle from degrees to radians using r = θπ/180. If we compile and run the program, we can see an object fired at 5 m/s at 45 degrees will travel just over 2.47 meters before it hits the ground:
$ f77 -o traj traj.f
$ ./traj
0.100000001 0.353565186 0.304541588
0.200000003 0.707130373 0.511083126
0.300000012 1.06069565 0.619624853
0.400000006 1.41426075 0.630166292
0.500000000 1.76782596 0.542707920
0.600000024 2.12139130 0.357249618
0.700000048 2.47495651 7.37910271E-02
0.800000072 2.82852173 -0.307667732
Constant values
The trajectory program used a value for π that is a known value, at about 3.14159. If the value won’t change, you don’t have to put it in a variable; instead, you can store it as a PARAMETER
. Here is a simple example:
PROGRAM PI0000
PARAMETER(PI=3.14159)
PRINT *, PI
END
This is similar to using a #define
in the C programming language. The parameter will look like any other variable, but you cannot change its value.
$ f77 -o pi pi.f
$ ./pi
3.14159012
Reading values
It would be nice to write a program once and run it multiple times with different values, where it prompts the user for the values each time, to see how the result changes. FORTRAN 77 has a companion to the PRINT
statement, which prints values to the output; READ
can interpret values from the user and store them in variables.
To read a value, give the READ
statement followed by a variable to store the result into. The READ
statement will use the *
format option to use the appropriate conversion; if you’re using READ
with an INTEGER
variable, the value is stored as an integer, and so on.
Let’s demonstrate this by writing a short program to convert temperatures from Celsius to Fahrenheit. For any temperature c, we can find the temperature f using f = (9/5)c + 32:
PROGRAM FAHR
REAL F,C
PRINT *, 'ENTER TEMP IN C'
READ *, C
F = C * 9.0 / 5.0 + 32.0
PRINT *, C, 'CELCIUS IS', F, 'FAHRENHEIT'
END
If we compile this program, we can run it as many times as we need to convert several temperatures to Fahrenheit, such as the freezing and boiling points of water:
$ f77 -o fahr fahr.f
$ ./fahr
ENTER TEMP IN C
0
0.00000000 CELCIUS IS 32.0000000 FAHRENHEIT
$ ./fahr
ENTER TEMP IN C
100
100.000000 CELCIUS IS 212.000000 FAHRENHEIT
One more thing
I can’t end the primer without showing a unique feature in FORTRAN 77: the computed GO TO
statement. This is a very interesting construct that can be useful in the rare times that you need it, but otherwise a subtle way to add new bugs when you don’t need to use it.
The computed GO TO
takes an integer variable and a list of line labels. If the variable has the value 1, then the GO TO
jumps to the first label in the list. As you might guess, the computed GO TO
jumps to the second label if the variable has the value 2. It gets tricky if the variable has a value less than 1, or if you have fewer labels in the list than the value.
We can use the computed GO TO
to calculate the Collatz Conjecture. The Collatz algorithm says for any positive even number, divide by 2; for any odd value, multiply by 3 and add 1. Collatz proposes that this simple algorithm will always result in 1 as the final value.
To determine if a value is odd or even, we can use the modulo function, which returns the remainder after division. The modulo of n/2 will be 0 if the number is even, and 1 if the number is odd. If we add 1 to the modulo, we’ll have 1 if even and 2 if odd. That’s a good use case for the computed GO TO
:
PROGRAM COLLATZ
INTEGER N,M
C GET NUMBER
10 PRINT *, 'ENTER STARTING VALUE'
READ *, N
IF (N.LT.1) THEN
PRINT *, 'CANNOT BE LESS THAN 1'
GOTO 10
END IF
C COLLATZ STARTS HERE
20 PRINT *, N
IF (N.EQ.1) GOTO 50
M = MOD(N,2) + 1
GO TO (30,40), M
30 N = N / 2
GO TO 20
40 N = 3 * N + 1
GO TO 20
50 END
If we compile and run the program, we can see that at least the numbers 1, 2, and 3 eventually fall to 1:
$ f77 -o collatz collatz.f
$ ./collatz
ENTER STARTING VALUE
0
CANNOT BE LESS THAN 1
ENTER STARTING VALUE
1
1
$ ./collatz
ENTER STARTING VALUE
2
2
1
$ ./collatz
ENTER STARTING VALUE
3
3
10
5
16
8
4
2
1
While this is a neat demonstration of the computed GO TO
, most programs will benefit from using some other arrangement. For example, the Collatz program can be written much simpler without the computed GO TO
:
PROGRAM COLLATZ
INTEGER N
C GET NUMBER
10 PRINT *, 'ENTER STARTING VALUE'
READ *, N
IF (N.LT.1) THEN
PRINT *, 'CANNOT BE LESS THAN 1'
GOTO 10
ENDIF
C COLLATZ STARTS HERE
20 PRINT *, N
IF (N.EQ.1) GOTO 50
IF (MOD(N,2).EQ.0) THEN
N=N/2
ELSE
N=3*N+1
ENDIF
GOTO 20
50 END
This uses an IF
…ELSE
block to perform n/2 for even numbers, and 3n + 1 for odd numbers. The blocks make the program’s flow more clear, and gives the same output without using a computed GO TO
:
$ f77 -o collatz2 collatz2.f
$ ./collatz2
ENTER STARTING VALUE
0
CANNOT BE LESS THAN 1
ENTER STARTING VALUE
3
3
10
5
16
8
4
2
1
More to explore
This is just a primer to the FORTRAN 77 language to create programs. There’s more to the language than just what I’ve shown here – but now that you have the basics, you should be able to create useful programs to solve a variety of problems.
To learn more, Oracle provides a free online language reference guide that provides an excellent description of FORTRAN 77. Skip ahead to the intrinsic functions for a list of functions that are built into the FORTRAN 77 standard library. You can also use this guide to learn about more advanced features of FORTRAN 77, including arrays, functions, subroutines, files, and formatted input and output.
The GNU Compiler Collection also has the GFortran documentation available in their online documentation collection. Make sure to match the version number of the documentation to the version of GFortran that you have installed on your system. You can check what version you have installed with the --version
option:
$ gfortran --version
GNU Fortran (GCC) 14.2.1 20240801 (Red Hat 14.2.1-1)
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
In this case, I have GFortran 14.2, and you can find the GFortran 14.2 documentation via this direct link.