DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

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
Enter fullscreen mode Exit fullscreen mode
$ gfortran -std=legacy -o hello hello.f
$ ./hello
 HELLO, WORLD!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Which prints:

./loop
 LOOP START
 I=
          10
 I=
          12
 I=
          14
 I=
          16
 I=
          18
 I=
          20
 LOOP DONE
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (6)

Collapse
 
marcellourbani profile image
Marcello Urbani

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

Collapse
 
taw profile image
Tomasz Wegrzanowski

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

Collapse
 
marcellourbani profile image
Marcello Urbani

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

Collapse
 
dforgeas profile image
dforgeas

I noticed that FIZZBUZZ still prints the multiples, 3, 5, 6, 9, 10 and so on. Is there a way to skip line 40?

Collapse
 
taw profile image
Tomasz Wegrzanowski

Oh wow, you're right, I totally missed that. Somehow I thought CONTINUE in Fortran is like continue 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 Python pass).

The correct thing seems to be CYCLE instead of CONTINUE.

Collapse
 
dforgeas profile image
dforgeas • Edited

I just tried CYCLE and indeed:

           1
           2
 FIZZ    
           4
 BUZZ    
 FIZZ    
           7
           8
 FIZZ    
 BUZZ    
          11
Enter fullscreen mode Exit fullscreen mode

By the way thanks for the terrific "speedrun". I'm looking forward to reading it all.