Let's look at an example of a complete program in FORTRAN and compare running it using the different compilers available on MTS.
Emirps are prime numbers that when their digits are reversed they make a different prime. An example is 13, which when reversed is the prime 31. The task on Rosetta Code is to:
- show the first twenty emirps.
- show all emirps between 7,700 and 8,000
- show the 10,000th emirp
Writing the program
I think it's interesting to describe how I wrote the program and got it to work. On modern systems I would tend to dive in and start writing small pieces of code and associated tests, TDD style. On MTS I favour writing the program offline first, import it into MTS and then get it working - this is partially due to being less comfortable with the MTS editor but also to match the usage pattern for MTS when it was in use; then terminal time was expensive so you would try to get more done before reaching for the computer.
So I started by sketching out what routines I'd need
* Three subroutines
3to test each of the tasks given in the problem
* A function
EMIRP to test if a number was an emirp. This would reverse the digits and if different from the original, test if both were prime.
* A function
PRIME to test if a number was a prime
* A function
REVRSE to reverse a number
I used Emacs on my host OS to write the routines - was pleasantly surprised to find it still supports FORTRAN 66 syntax. This took around 45 minutes.
The prime number test I used was a brute force approach so not particularly efficient:
C *** TEST IF A NUMBER IS PRIME *** LOGICAL FUNCTION PRIME(N) INTEGER N C DEAL WITH NUMBERS <= 3 IF (N .LE. 1) GOTO 200 IF (N .EQ. 2 .OR. N .EQ. 3) GOTO 100 C CHECK IF DIVISIBLE BY 2 OR 3 IF (MOD(N,2) .EQ. 0) GOTO 200 IF (MOD(N,3) .EQ. 0) GOTO 200 C SEE IF DIVISIBLE BY 5, 7, ..., UP TO APPROX SQRT(N) 10 DO 20 I=5,999999,2 IF (I*I .GT. N) GOTO 100 IF (MOD(N,I) .EQ. 0) GOTO 200 20 CONTINUE 100 PRIME = .TRUE. RETURN 200 PRIME = .FALSE. RETURN END
Reversing the digits was done by taking the modulus of the number with 10 and then appending each digit to the end:
C *** REVERSE AN INTEGER'S DIGITS *** INTEGER FUNCTION REVRSE(N) INTEGER N INTEGER M,R C M IS COPY OF N FROM WHICH WE TAKE DIGITS C R IS REVERSED DIGITS M = N R = 0 C LOOP UNTIL NO MORE DIGITS 10 IF (M .LT. 1) GOTO 100 C TAKE LAST DIGIT FROM M AND APPEND TO R R = R * 10 R = R + MOD(M, 10) M = M / 10 GOTO 10 100 REVRSE = R RETURN END
The emirp function was then a simple case of calling the two functions:
C *** TEST IF AN INTEGER IS AN EMIRP *** LOGICAL FUNCTION EMIRP(N) INTEGER N C EXTERNAL FUNCTIONS INTEGER REVRSE LOGICAL PRIME C R CONTAINS REVERSED DIGITS OF N INTEGER R R = REVRSE(N) C N AND R MUST BOTH BE PRIME AND NOT THE SAME VALUE IF (N .EQ. R) GOTO 200 IF (.NOT. PRIME(N)) GOTO 200 IF (.NOT. PRIME(R)) GOTO 200 100 EMIRP = .TRUE. RETURN 200 EMIRP = .FALSE. RETURN END
The test functions are simple loops: here I show just the second test as an example.
C *** SHOW EMIRPS BETWEEN 7,700 AND 8,000 *** SUBROUTINE TEST2 C EXTERNAL FUNCTION LOGICAL EMIRP C N IS NUMBER TO TEST INTEGER N 10 DO 100 N=7700,8000 IF (EMIRP(N)) CALL SHOW(N) 100 CONTINUE RETURN END
I factored out the printing to another routine as each test needs to do this:
C *** DISPLAY AN INTEGER *** SUBROUTINE SHOW(N) INTEGER N WRITE(6,50) N 50 FORMAT(I10) RETURN END
Getting it running with
Next I imported the program into MTS (using the emulated card reader).
I then ran
*IF66 and did
/compile emirp.f. Lots of syntax errors - I forgot the maximum length for identifiers was 7 characters and that I needed to declare functions used externally.
Next I tried to test the individual routines - here
*IF66 was very useful, as I could call eg the reverse routine with some test input and see its output immediately. Some more problems to fix here, eg function parameters seem to be pass by reference in FORTRAN. An error in the DO-loop in the prime testing function lead to an infinite loop that I could not get out of with
PA2, so a trip to the operator's console to
STOP the MTS session was needed.
Finally I got it running after about another hour. The first two tasks were quick to execute but the third, to find the 10,000th emirp, ran for over 10 minutes without returning so I killed it. Time to run this using the compilers and see how they perform.
Using the compiler
First I tried it using FORTRANG:
# $run *ftn scards=emirp.f # Execution begins 20:05:30 No errors in PRIME No errors in REVRSE No errors in EMIRP No errors in SHOW No errors in TEST1 No errors in TEST2 No errors in TEST3 No errors in MAIN # Execution terminated 20:05:30 T=0.034 # $run -load # Execution begins 20:05:34 13 ... 948349 # Execution terminated 20:05:43 T=8.82
So just over 0.03 seconds of CPU time to compile and nearly 9s to run all tests.
Let's try it with FORTRANH by using the
opt parameter to
*ftn. Note that the output of the compiler is quite different as FORTRANH is a completely different product:
# $run *ftn scards=emirp.f par=opt=h # Execution begins 20:06:12 **** No errors for PRIME **** WED DEC 17/14 20:06:13 **** No errors for REVRSE **** WED DEC 17/14 20:06:13 **** No errors for EMIRP **** WED DEC 17/14 20:06:13 **** No errors for SHOW **** WED DEC 17/14 20:06:13 **** No errors for TEST1 **** WED DEC 17/14 20:06:13 **** No errors for TEST2 **** WED DEC 17/14 20:06:13 **** No errors for TEST3 **** WED DEC 17/14 20:06:13 **** No errors for MAIN **** WED DEC 17/14 20:06:13 # Execution terminated 20:06:12 T=0.092 # $run -load # Execution begins 20:06:30 13 ... 948349 # Execution terminated 20:06:35 T=4.607
Compilation took almost three times as long, but the program ran 48% faster so there's definitely some good optimisation taking place. The compilation time difference may seem insignificant, but in an environment where you are paying for CPU time and working on a large program that requires many recompiles during development the costs would mount up.
Next step would probably be to improve the algorithm for testing for primeness, but I'll leave that for now.
Final thoughts on FORTRAN
FORTRAN 66 has not aged well - its fixed format lines, lack of character/strings, unstructured design and implicit variable types make it hard to use for large scale programming. But it's a simple language to pick up and use, and you can see that if you want to solve a numerical problem it can be very productive. Coupled with the optimising compiler and support tools available you can understand why it was the lingua franca of its time, rather like C today.
Full source code for this program is on github.