OS/8 FORTRAN Implementations of Project Euler Problem #1
This Is Not Fortran. THIS IS FORTRAN!
FORTRAN is one of the oldest high-level programming languages if we define "high-level" as "above assembly language." It is one of the three oldest languages in continuous common use from its creation to today, the other two being COBOL and LISP. As I write this article, FORTRAN is 60 years old!
FORTRAN has evolved considerably over that span. We will be using two different dialects in this article, those being the FORTRAN II and FORTRAN IV implementations that come with OS/8 V3D, neither of which is much like modern Fortran at all.
When you speak of old-style FORTRAN, you write it in all caps because machines of that era generally didn't handle lowercase well, or at all. Fortran wasn't officially spelled with mixed case until the release of Fortran 90.
FORTRAN IV
If you are still a working programmer and go "way back" with FORTRAN, you probably started with FORTRAN 77, which wasn't finalized until 1978, despite the name. Work was begun on FORTRAN IV in 1961 and completed in 1965, making FORTRAN IV much older and therefore far more primitive. The language shipping with OS/8 is actually a version of FORTRAN 66, despite the name. Quoting from one of the available OS/8 FORTRAN IV manuals:
"The OS/8 FORTRAN IV language generally conforms to the specifications for American National Standard FORTRAN X3.9-1966."
This is because the original version of IBM FORTRAN IV was selected by the predecessor to ANSI (the American Standards Association) as the de facto standard upon which to base its Standard FORTRAN, published in 1966, making it one of the computing industry's first vendor-independent programming languages. Since OS/8 postdates 1966 considerably, and it is a programming environment for a non-IBM computer, it is unsurprising that it actually implements the vendor-independent FORTRAN 66 language. This practice of continuing to use the FORTRAN IV name was common at the time. We will continue to use that name in this article to avoid confusion with the OS/8 manuals' nomenclature.
FORTRAN IV is considerably more complicated and powerful than OS/8 BASIC or U/W FOCAL, languages we used for prior solutions to this problem, but we will only be using a sliver of that power. It is vastly easier to use than PAL8 assembly language, which required that I spend about 3 days working on the solution. Each of the FORTRAN solutions in this article took me only about a few hours to create, including the time to learn enough of the language and runtime system to write, build, and debug it, since I hadn't written any FORTRAN to speak of before starting on this article!
I say all of this to point out that FORTRAN strikes a very nice balance between simplicity and power, which makes it well suited to more complicated programs than you'd normally want to tackle with FOCAL or BASIC, yet without giving up much of the power you get with assembly language. The Adventure game we include with the PiDP-8/I distribution of OS/8 was written in FORTRAN IV.
Implementation
Here is the solution I came up with:
DO 10 I = 3, 999
10 IF (MOD(I, 3) .EQ. 0 .OR. MOD(I, 5) .EQ. 0) ITOTAL = ITOTAL + I
WRITE (4,20) ITOTAL
20 FORMAT (' TOTAL: ', I6)
On the plus side, this is the shortest solution in the series so far.
On the minus side, this code looks pretty ugly to programmers who haven't seen code older than a few decades. This is primarily because FORTRAN IV still hews pretty closely to its roots as a punched card language, where each line of FORTRAN code was assumed to be encoded on a single 80-column IBM card. IBM's history with punched cards goes back to its very founding as the Tabulating Machine Company in 1896, which produced punched-card-driven machines. PDP-8 computers generally didn't used punched cards, but its FORTRAN compilers had to conform to these assumptions.
The first rule for reading old-style FORTRAN is that the first 6 columns of a line have a special meaning. The first 5 are reserved for a statement number, of which I've used two above, 10
and 20
; alternately, you can make a line a comment by putting a C
in the first column. The sixth column is for a continuation character: if there is anything other than a space in that column, the compiler takes that line as a continuation of the previous one. A statement must begin in column 7 or later.
FORTRAN will let you indent lines, but it isn't common in old-style FORTRAN because you're limited to columns 7 through 72 for your code. Anything after that is simply ignored by the FORTRAN compiler.
Columns 73 through 80 were commonly used for a sequence number in case some poor soul drops a deck. A specialized device called a card sorter would use this information to put the cards back in their proper order. We don't have that problem with OS/8 FORTRAN: either we store programs on magnetic media or punched tape — or at least simulations of them within SIMH — neither of which is liable to become deranged when dropped, at least not in a way that we can fix with a card sorter.
Let's go through those lines one by one:
DO 10 I = 3, 999
FORTRAN IV uses the DO
keyword for iterated loops, and in fact for loops of all sorts, rather than the more common FOR
keyword we find in the BASIC, FOCAL, and C solutions to this problem.
The number following the DO
keyword is the statement number that ends the loop, which we'll get to on the next line.
Like BASIC and FOCAL, the OS/8 implementation of FORTRAN IV will let you use variables without explicitly declaring them first, but unlike those two, FORTRAN does have a way to declare variables. Since we don't use that feature in this program, the FORTRAN IV compiler infers the type of variables based on their first letter. Any variable beginning with I
through N
is treated as an integer, and anything else as a floating-point variable.
As with both FOCAL and BASIC, iterated loops like this are inclusive of their given bounds, so the last iteration of the loop is I=999
here instead of i < 1000
as in the C version.
10 IF (MOD(I, 3) .EQ. 0 .OR. MOD(I, 5) .EQ. 0) ITOTAL = ITOTAL + I
This line is why this solution is so much shorter than that for the others.
First off, we have a logical OR operation in FORTRAN IV. This is huge! It saves all that GOTO
silliness required in the BASIC and FOCAL solutions.
Second, although FORTRAN IV has the same sort of arithmetic IF
statement as FOCAL, it also has a logical IF
form that simply executes the statement after the parenthesized expression when that expression is true. Unlike more modern languages like C, we can only have one statement here, but for this problem, we only need one. FORTRAN IV programmers that needed to execute more than one statement in a logical IF
had to use a GOTO
or subroutine call until FORTRAN 77 arrived, which finally added block IF
statements.
The line begins with a statement label of 10, which was referred to in the DO
statement, which simply means that when this line is done executing, we do the next iteration of the loop. There is nothing special about the number 10. It could be any integer between 1 and 99999, inclusive.
This line also shows off one of the major advantages of even a relatively primitive form of the FORTRAN language over any of the PDP-8 assemblers: you get a substantial standard library of common routines, as shown here with the MOD
function, which returns the remainder when you divide its second argument into its first. (There is also an AMOD
variant for REAL
arguments.) Thus we get the extra facilities of a user-focused programming language like FOCAL or BASIC with a large fraction of the speed and power of assembly language.
Here again we use a variable without first declaring it or giving it a value: because it begins with I
, the FORTRAN IV compiler assumes it's an integer and gives it an initial value of 0, which is exactly what we want here.
This line squeaks under the 72 column limit with little room to spare!
WRITE (4,20) ITOTAL
The only thing that could be uglier about this line is if it was system-dependent, as in early versions of FORTRAN.
The 4 value means "the terminal." You just have to memorize that value from the manual. There are other values here, with 1-4 being preassigned by the FORTRAN IV language and 5-9 reserved for the programmer's use.
The 20 value is another statement number, this time giving the line where the corresponding FORMAT
lives. That's right, you need at least two lines of code to do formatted output in old-style FORTRAN. Yuck!
20 FORMAT (' TOTAL: ', I6)
And there's statement 20, the FORMAT
statement referred to by the WRITE
statement.
The first argument is a text literal. Here again we have another ugly bit: the first character has special meaning and is not printed directly. A space means "do a CR + LF after writing this line." As with the device number constants, the legal values here are just something you have to memorize if you want to use FORTRAN.
The next argument is more sensible: I
means it should expect an integer from the WRITE
statement, which is ITOTAL
in this case. 6 is the number of characters to write, which we happen to know is how wide the correct answer to Project Euler Problem #1 is. If we weren't sure, we could have used something like I12
and just let it pad the answer out with spaces.
The fact that we must give a number for the field width is another artifact of the fact that this language comes out of the punched card era: columnar data formats were very common for data storage, much like the rules I laid out above for formatting a FORTRAN punched card. You would define what fields were in which columns of the punched cards you used to store your program's data, encoded in the FORMAT
statement.
There is one virtue of this split between WRITE
and FORMAT
statement: you can reuse a FORMAT
statement between a WRITE
and its corresponding READ
, so that if the data format changes, you only need to update it once, making it one of the earliest uses of the DRY principle in software development.
Executing It
To compile, link, load, and execute the FORTRAN IV version of this program on your system:
.EXE PEP001.FT
(See the companion article Getting Text In for information on getting that PEP001.FT
source code file onto the OS/8 disk.)
If you don't have any other PEP001.*
files on DSK:
, you can leave the file extension off, as OS/8's EXECUTE command will iterate through the list of extensions it knows how to work with.
After executing the program once, you will have a PEP001.LD
file, which is input to the FORTRAN Run Time System (FRTS) loader:
.R FRTS
*PEP001$ ⇠ $ = press Escape to load, link, and execute the program
There is a much more complicated procedure for building FORTRAN programs in the OS/8 manuals. It is useful when:
- you have a program composed of multiple modules
- you need to use OS/8 FORTRAN IV's overlay system
- you want a
.SV
file that you can load and run with OS/8'sR
command rather than an.LD
file
FORTRAN II
OS/8 also includes a much older (1958) form of the language which it calls FORTRAN II, though it is probably closer to what the American Standards Association defined in 1966 as Basic FORTRAN, being IBM FORTRAN II with the machine-specific bits stripped out.
Implementation
Because the FORTRAN II language, its library, and its runtime system are much more primitive than FORTRAN IV, we end up with a much longer program:
DO 10 I = 3, 999
IF (IREM(I / 3)) 5, 15, 5
5 IF (IREM(I / 5)) 10, 15, 10
15 J = J + I
IF (J - 1000) 10, 18, 18
18 WRITE (1,19) J
19 FORMAT (I4, ' + ')
J = 0
10 CONTINUE
WRITE (1,20) J
20 FORMAT (I4)
END
We're going to have to describe all but the first line all over again, since little is shared with the FORTRAN IV version, which incidentally is why I didn't save much time writing the FORTRAN II version after having written the FORTRAN IV version.
Line numbers in this version are the same where their function is the same as in the FORTRAN IV version. New line numbers are used for code that has no direct correspondence to the FORTRAN IV version.
IF (IREM(I / 3)) 5, 15, 5
5 IF (IREM(I / 5)) 10, 15, 10
Instead of a MOD
ulo function, we get IREM
in FORTRAN II, meaning "integer remainder". We have to pass it the result of a division operation.
The more significant difference here is that FORTRAN II doesn't have the logical IF
statement, only the arithmetic IF
. It also lacks the boolean OR operator, requiring that we write our code much as we did for the FOCAL version, except that FORTRAN doesn't let you omit the statement numbers for cases you don't wish to consider.
These two differences mean that we need two statements to make the key test for this program, with the first transferring control to the second if (and only if) it didn't get zero for the integer remainder. We then try dividing I
by 5 in that case, transferring to the end of the loop (statement 10) if we also get nonzero here.
15 J = J + I
This is our new adder, used by both IREM
lines above when they get a 0 result.
There's more going on here than is obvious, though.
First off, FORTRAN II doesn't have the INTEGER
statement to force a variable's type. We absolutely must use a leading I
through N
to have an integer variable in FORTRAN II. This variable is the one called ST
in the other versions of this program that also use this subtotaling trick, short for "subtotal". We can't call it that under FORTRAN II, alas.
Second, we have to fall back to the subtotaling trick used in the C and PAL8 versions of this program, because OS/8 FORTRAN II is limited to 12-bit arithmetic. CC8 shares this limitation because it's actually built atop the same base as OS/8's FORTRAN II, which does not assume you have the EAE option or have a fallback library for emulating it in software as the OS/8 version of FORTRAN IV does.
This 12-bit limitation shows up in another interesting way in OS/8 FORTRAN II: line numbers are limited to 1 through 2047, even though the language still restricts line numbers to columns 1 through 5 inclusive. That is the maximum positive value you can store in a two's-complement 12-bit integer.
IF (J - 1000) 10, 18, 18
FORTRAN II lacks a "greater than" operator, so we must do a subtraction here to test whether the subtotal has become greater than or equal to 1000.
18 WRITE (1,19) J
19 FORMAT (I4, ' + ')
J = 0
Each time the subtotal cracks 1000, we write it out along with a plus sign and reset the subtotal to 0.
You may notice that the 4
constant meaning "the terminal" in FORTRAN IV's WRITE
statement changes to 1
here. That's what I meant by my comments above about FORTRAN being machine-dependent in its early versions. The device numbers would change between different machines, so that otherwise portable FORTRAN code had to be modified to work on the new machine, simply because the device numbers were different. FORTRAN 66 didn't do away with device numbers — that wart wouldn't be fixed until Fortran 90 — but it did at least standardize their meaning.
We've changed the width of the integer field from 6 to 4, again coming back to that maximum positive value of 2047.
I was unable to find a way to suppress the newline here, which means the output of this program can't simply be pasted into bc(1)
to give the final total. You have to strip all the newlines out and replace multiple spaces with a single space, at least with my local version of bc
.
10 CONTINUE
We reuse statement number 10 here even though the line does something different from the FORTRAN IV version because it serves the same purpose, marking the end of the DO
loop.
The CONTINUE
statement in FORTRAN serves a somewhat different purpose than continue
in C or any of the many languages that share its syntax for for
loops. Strictly speaking, it is a do-nothing statement, serving only to give the line number something to hang onto, since a bare line with 10
on it would be illegal in FORTRAN. Nevertheless, it is often used as you see it here, to go to the next iteration of the loop, just as in C. It's just that instead of saying "continue
" somewhere in the loop body, you say "GOTO
the CONTINUE
line" instead.
(Yes, GOTO
: an arithmetic IF
is basically a 3-way GOTO
based on the value of an expression.)
WRITE (1,20) J
20 FORMAT (I4)
As with the other versions of this program that use the subtotaling trick, we have to print the subtotal one last time in case it's nonzero when the main loop exits.
END
Unlike with FORTRAN IV, we must have an explicit END
statement in a FORTRAN II program.
I hope you now look at the FORTRAN IV version with much less distaste, knowing that it definitely can be worse. :)
That said, this is still vastly easier to understand than the PAL8 version.
Executing It
To compile, link, load, and execute the FORTRAN II version of this program on your system:
.R FORT
*PEP001/G ⇠ compile, assemble, load, and execute PEP001.FT
We cannot use the OS/8 EXECUTE
command here because it uses the file name extension *.FT
for both FORTRAN II and FORTRAN IV source files, which are incompatible. OS/8 assumes you mean FORTRAN II only if you build it without FORTRAN IV support, which is not the default for the PiDP-8/I distribution of OS/8.
Other Programs in the Series
I've solved this same problem in many other languages available for the PiDP-8/I:
You may find it interesting to compare their solutions.
Conclusion
FORTRAN IV is a surprisingly powerful language given its age. Compared to a modern programming language, it looks horrible, but it gives code that runs a lot faster than BASIC or FOCAL while being far easier to write than PDP-8 assembly code.
FORTRAN II is primitive enough to be considered a high-level alternative to PAL8, with a much better runtime and standard library. Unless you need your program to be compiled, assembled, linked, and run in just a few pages of PDP-8 memory, though, FORTRAN IV is a much better way to go.
License
Copyright © 2017 by Warren Young. This document is licensed under the terms of the SIMH license.