Fifty Problems with Fortran

In which an anachronism is enjoyed

I find arguments about programming languages incredibly tedious, and I don’t think I’m alone on that. There are languages that I enjoy, languages I’m good at using, and languages that are in some vaguely objective sense ‘good’ for a particular task (or for every task, supposedly). Why should they all be the same? Certainly my set won’t match yours, even if I somehow managed internal consistency.

For a while the R statistically-focused programming language fit all three of those categories for me. I find it fun, I’m good at it (or at least good enough), and it’s good for a lot of stuff that I was doing. Unfortunately (by which I mean, extremely fortunately) a few years ago I got a job that heavily involves working in R and so while it’s nice to be using something I like it makes it very hard to be enthusiastic about it outside of a work context.

There’s no reason why I have to do programming in my spare time, of course. I’m not the first person to point out that that is a corrosive idea that promotes both burnout and the exclusion of people who don’t have that kind of spare time. That said sometimes I do want to make something, useful or otherwise.

All my mastodon bots, along with Real Time Anywhere are made with python. Python is absolutely good for stuff, and I’m handy enough with it, but for reasons I can’t articulate I don’t find it fun.1 I like C and C++, but it’s been a while and I don’t trust myself with them, which is kind of the opposite problem to python. So what to do?

Actually I have already talked about this: I’ve long wanted to learn FORTRAN. Or Fortran, because unless I got to use a real old mainframe for some reason I’m not particularly attached to the older capitalisation-era versions. I just think it’s neat.

I’ve also recently been introduced to Project Euler, a long-running collection of programming problems in the same vein as Advent of Code but without the seasonal focus or through-plot. It has 841 problems at time of writing, of which I’ve completed the first page of fifty, all of them in Fortran. It’s really fun!

In practise Project Euler has been a lot of writing out subtly different versions of the Sieve of Eratosthenes, and functions for extracting individual digits from numbers at various combinations of base and endianness, but I quite enjoy that.

function getDigit(base, digit, val)
    implicit none
    integer, intent(in) :: base
    integer, intent(in) :: digit
    integer, intent(in) :: val
    integer :: getDigit
    getDigit = mod(val / (base**(digit-1)), base)
end function getDigit

Fortran has some quirks. This function, a little-endian multi-base integer digit extractor which I would probably write as a pre-processor macro in C, is mostly boilerplate. It could be condensed a bit but really I don’t mind. Someday I should talk about my feelings on boilerplate code, especially in the context of tools like Github Copilot, but not today.

function easyPrimeCount(x)
    ! Hacky program to calculate an upper bound for the number of primes le x
    implicit none
    integer(kind=si_kind), intent(in) :: x
    integer(kind=si_kind) :: easyPrimeCount
    real :: rx
    if (x .le. 100_si_kind) then
        easyPrimeCount = 25_si_kind
    else if (x .le. 1000_si_kind) then
        rx = real(x)
        easyPrimeCount = floor((rx / log(rx)) * 1.26, si_kind)
    else if (x .le. 100000_si_kind) then
        rx = real(x)
        easyPrimeCount = floor((rx / log(rx)) * 1.18, si_kind)
        rx = real(x)
        easyPrimeCount = floor((rx / log(rx)) * 1.11, si_kind)
    end if
end function easyPrimeCount

One of my favourite Fortran features, but one which I wish were much more powerful, is kinds. This is a mechanism for choosing the size of integers and other variables - your int32 and doubles and the like. What’s fun to me about Fortran’s approach is that the way you select how many bits you need.

For the above function (which gives a rough upper bound for the number of primes below x for the purpose of defining an array) the size of x is controlled by the parameter si_kind which is in turn defined like so:

integer, parameter :: si_kind = selected_int_kind(7)

The ‘7’ here means that we asking for integers with at least 7 digits of integer precision, which it will translate into the number of bits (or bytes, in the case of the compiler I’m using) that the variable should use. This is very much not the usual way of doing things in the other programming languages I’m familiar with. Normally I would be much more directly specifying the size, but this indirect method has it’s charms - especially when working with floating point (‘real’ in Fortran) numbers. Reckon you need about 10 digits of precision? Ask for that directly rather than looking up how long you need your doubles to be or what have you.

But there are limitations. To my knowledge it’s not practical to pass the ‘kind’ to a function, so you really need to either set the constant at a program/module level or write multiple copies of the same function for different levels of precision. Additionally the number of available options are quite limited. You can get 128 bit integers and reals (yay!) but no higher, which is a real shame as it still limits you to less than 40 decimal digits. It would be great if you could ask a hundred digits and have the compiler just figure it out.

Speaking of lots of digits I tried to avoid using arbitrary precision arithmetic when possible. The library I found for this was mpfun2020, which worked well enough but seemed too overkill for my purposes. There are probably other choices but I decided to hew towards using modular arithmetic and logarithms whenever the need arose, which worked well enough in most cases.

What is all this useful for? Probably very little, which is honestly the idea. You can use Fortran within R to write fast algorithms but that’s out of fashion these days in favour of C++-based solutions. It’s possible that someday I will encounter some Fortran at work - I certainly work at a place where it would be feasible! - but so far the oldest code I’ve run in to in that context dates from as recently as the late 90s.

Fortran just isn’t a general-purpose language. Maybe it was during the days of mainframes and punched cards, but that was because it had to be not because it was really designed for it. Today the language is still used for it’s original purposes, with a new emphasis on parallel calculations. This really interests me, again not in the sense that I will ever find it useful, but I not sure what project I will use to play around with it. I don’t really have the data sources for homebrew weather forecasting, at least not yet.

  1. Except for list comprehension, for some reason I enjoy that language feature specifically.↩︎

File under: