100 Languages Speedrun: Episode 31: Fortran

The first programming language was Fortran (FORmula TRANslator). Since then, a lot of languages called "Fortran " were created, each bringing it closer to modern programming and further away from its sources.

It would be difficult to actually run extremely old Fortran code, as that worked with punch cards, and data tapes. Even by the time of Fortran 77 a lot of the ancient Fortran features were either gone or marked as obsolete. But let's do our best - using GNU Fortran in legacy mode, and trying to make the code as old style as I can get away with.

Whenever I mention Fortran in this post, I mean the really old stuff. So don't tell me about Fortran 2018 and how it can do object oriented concurrent crypto mining while running in WASM on a tablet or whatever kids are into these days.

Hello, World!

Obviously we'll be writing the program in full caps, as lower case letters weren't invented until 1970s. Well, at least computers didn't usually have them, to save precious memory.

Unlike all modern, Fortran has fixed column layout. Here's one attempt at Hello, World:

C      PRINTS "HELLO, WORLD!"
       PROGRAM HELLO
 1     PRINT *, 'HELLO, WORLD!'                                         NICE
       END
$ gfortran -std=legacy -o hello hello.f
$ ./hello
 HELLO, WORLD!

What the hell is going on here?

  • column 1 is comment indicator - if there's anything non-blank there, the whole line is a comment. So you can have // or # or ; or -- comments in Fortran, whichever style you prefer! How convenient, right?
  • columns 2-6 are for optional line number, we'll be doing goto a lot
  • column 7 is line continuation indicator - if there's anything else than space (or 0 for some reason) it continues previous line
  • columns 8-72 are for the actual code
  • columns 73+ are ignored, so they're essentially also comments

Weird? We're just getting started.

Hollerith strings

Fortran 77 introduced strings delimited by quotation marks. How did Fortran programmers do strings before then? You're in for a treat!

The old syntax for strings was character count, followed by H, followed by the string. If you miscounted, and people did all the time, that was a syntax error.

I assure you, I'm not trolling, this was actually the thing.

C      PRINTS "HELLO, WORLD!"
       PROGRAM HELLO
 1     PRINT *, 13HHELLO, WORLD!                                        EVEN NICER
       END

Loop

Fortran 77 introduced some sort of structured programming. In the olden days, everything was based on line numbers. Take a look at this loop:

       PROGRAM LOOP
       PRINT *, 10HLOOP START
       DO 20 I = 10, 20, 2
 10    PRINT *, 2HI=
 20    PRINT *, I
 30    PRINT *, 9HLOOP DONE
       END

Which prints:

./loop
 LOOP START
 I=
          10
 I=
          12
 I=
          14
 I=
          16
 I=
          18
 I=
          20
 LOOP DONE

The DO 20 I = 10, 20, 2 means for i = 10 to 20 by 2 loop. The loop goes until line number listed in the DO statement, that is 20. After the loop is over it goes to next statement after statement numbered 20.

As for declaring variables, any variable starting with I, J, K, L, M, or N as integer. Any other name is a float. Oh and variable names could have at most 6 characters originally.

FizzBuzz

Now that we know how to loop and print, it's fairly straightforward to do a FizzBuzz:

       PROGRAM FIZZBUZZ
       DO 40 I = 1, 20
 10    IF (MOD(I,15).NE.0) GOTO 20
       PRINT *, 8HFIZZBUZZ
       CONTINUE
 20    IF (MOD(I,5).NE.0) GOTO 30
       PRINT *, 4HBUZZ
       CONTINUE
 30    IF (MOD(I,3).NE.0) GOTO 40
       PRINT *, 4HFIZZ
       CONTINUE
 40    PRINT *, I
       END

That prints creatively formatted FizzBuzz:

$ ./fizzbuzz
           1
           2
 FIZZ
           3
           4
 BUZZ
           5
 FIZZ
           6
           7
           8
 FIZZ
           9
 BUZZ
          10
          11
 FIZZ
          12
          13
          14
 FIZZBUZZ
 BUZZ
 FIZZ
          15
          16
          17
 FIZZ
          18
          19
 BUZZ
          20

MOD(I,15) is i % 15. .NE. is !=. CONTINUE goes to the next iteration of the loop.

This formatting is quite bad, so how could we improve it?

Double function

Let's write a very simple function, that doubles the integer it gets:

       PROGRAM DOUBLING
 10    FORMAT(7HDOUBLE(, I3, 2H)=, I4)
       DO 20 I = 1, 20
       J = DOUBLE(I)
 20    PRINT 10, I, J
       END

       FUNCTION DOUBLE(I)
       DOUBLE=I*2
       END

And run it:

./double
DOUBLE(  1)=   2
DOUBLE(  2)=   4
DOUBLE(  3)=   6
DOUBLE(  4)=   8
DOUBLE(  5)=  10
DOUBLE(  6)=  12
DOUBLE(  7)=  14
DOUBLE(  8)=  16
DOUBLE(  9)=  18
DOUBLE( 10)=  20
DOUBLE( 11)=  22
DOUBLE( 12)=  24
DOUBLE( 13)=  26
DOUBLE( 14)=  28
DOUBLE( 15)=  30
DOUBLE( 16)=  32
DOUBLE( 17)=  34
DOUBLE( 18)=  36
DOUBLE( 19)=  38
DOUBLE( 20)=  40

What's going on:

  • we finally get nicely formatted output, that we can use FORMAT statement to setup some format (in this case equivalent of "double(%3d)=%4d\n") - each FORMAT statement has a label (in this case 10), and it's used to refer to format number - these are not just for GOTO
  • PRINT 10, I, J uses the 10 FORMAT we setup before
  • FUNCTION DOUBLE(I) ... END defines a function
  • assigning to function name DOUBLE = I*2 sets which value will be returned, there's no direct equivalent of RETURN I*2

Fibonacci

Now that we know how to do a function, let's do a Fibonacci sequence. Oh wait, even Fortran 77 did not support recursive functions at all.

For now let's use some "modern" Fortran extensions to have recursion.

       PROGRAM FIBONACCI
 10    FORMAT(4HFIB(, I3, 2H)=, I10)
       DO 20 I = 1, 20
       J = FIB(I)
 20    PRINT 10, I, J
       END

       RECURSIVE FUNCTION FIB(I) RESULT(A)
       IF (I-2) 30,30,40
 30    A=1
       RETURN
 40    A=FIB(I-1)+FIB(I-2)
       END

It prints what we'd expect:

FIB(  1)=         1
FIB(  2)=         1
FIB(  3)=         2
FIB(  4)=         3
FIB(  5)=         5
FIB(  6)=         8
FIB(  7)=        13
FIB(  8)=        21
FIB(  9)=        34
FIB( 10)=        55
FIB( 11)=        89
FIB( 12)=       144
FIB( 13)=       233
FIB( 14)=       377
FIB( 15)=       610
FIB( 16)=       987
FIB( 17)=      1597
FIB( 18)=      2584
FIB( 19)=      4181
FIB( 20)=      6765

There are a few things going on here:

  • RECURSIVE FUNCTION FIB(I) RESULT(A) - declares recursive function, we also need to declare return variable, as otherwise FIB=FIB(I-1)+FIB(I-2) would be too confusing
  • RETURN - we can return early
  • IF (I-2) 30,30,40 - the "arithmetic if", you pass a number, and three goto labels depending if the number is negative, zero, or positive - that's how Fortran programmers used to do if (i <= 2) equivalent back in the days

Fibonacci without recursion

But back in the olden days, recursion was not allowed. Fortunately we already know the loop-based algorithm:

       PROGRAM FIBONACCI
 10    FORMAT(4HFIB(, I3, 2H)=, I10)
       DO 20 I = 1, 20
       J = FIB(I)
 20    PRINT 10, I, J
       END

       FUNCTION FIB(I)
       K1 = 0
       K2 = 1
       DO 30 J = 1, I
       K3 = K1 + K2
       K1 = K2
 30    K2 = K3
       FIB=K1
       END

It outputs the same thing. Any variable starting with I, J, or K is also an integer, so we didn't need to declare anything.

Should you use Fortran?

As long as we're talking about these ancient versions, obviously not.

Some old languages like Forth or PostScript could enjoy a second life as an esoteric language, but I don't think old Fortran even has much potential there.

Supposedly modern Fortran generates slightly faster numerical code than C/C++ in some cases, so some numerical calculation libraries are still coded in Fortran (from what I remember, Fortran arrays can be assumed to not overlap, while C/C++ pointers can point anywhere, so Fortran compiler can do a few more optimizations), even if they are generally used from other languages. This is a somewhat marginal use, and you can probably get matching performance with some compiler hints, but in any case, this episode is only about old Fortran anyway.

Code

All code examples for the series will be in this repository.

Code for the Fortran episode is available here.