A look back: FORTRAN 77

0

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.

original image

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.

original image

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 IFELSE 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.

Leave a Reply