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"
) - eachFORMAT
statement has a label (in this case10
), and it's used to refer to format number - these are not just forGOTO
-
PRINT 10, I, J
uses the10 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 ofRETURN 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 otherwiseFIB=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 doif (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.
Top comments (6)
If I remember correctly, the main reason why fortran code is faster than C is memory allocation and array access.
Fortran has no allocation, everything is static.
So if I want to access element (1,7) of an array of 1024x1024 I know the address at compile time. In C++ at best I know the offset from the address on the heap
I think you're misremembering this. Fortran has ALLOCATE, allocates data on heap and stack like every other language else, and in any case it's mostly used to implement libraries which are called by other languages and have memory allocated the regular way. And static wouldn't even be faster on x86.
C/C++ requires some extra annotations for best performance, while Fortran will work out of the box (due to memory layout and restricted pointer aliasing rules more appropriate for numerical calculations), but I don't think you can find any benchmark in 2022 that still shows meaningful advantage of Fortran over properly annotated C/C++.
My point about static was that offsets and addresses are known at compile time.
On any architecture runtime is faster if the calculations are already done.
Moot point if you do allocate on the heap
I noticed that FIZZBUZZ still prints the multiples, 3, 5, 6, 9, 10 and so on. Is there a way to skip line 40?
Oh wow, you're right, I totally missed that. Somehow I thought
CONTINUE
in Fortran is likecontinue
in C and other C-likes, and didn't pay attention to the output.Turns out
CONTINUE
in Fortran does literally nothing, and it's only used as placeholder (like Pythonpass
).The correct thing seems to be
CYCLE
instead ofCONTINUE
.I just tried CYCLE and indeed:
By the way thanks for the terrific "speedrun". I'm looking forward to reading it all.