I have extracted a couple of demonstration programs from the CSIRAC library. As these tend to have minimal (if any) comments, I have added suitable comments. The library routines were provided to me by the University of Melbourne, Engineering and Information Technology Collection, and are distributed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0). To simplify these as examples, I have deleted the PRIMARY and CONTROL programs from the listings of the tapes, and added comments in blue. The original 12-hole tapes can be downloaded and run with the emulator.
This program reads in the setting of NA and NB switch registers, treating them as integers, it multiplies them together, and prints out the result on the printer.
printer subroutine | |||||||
1 | 0T | set loading address to start at 32 (1,0) | |||||
2S | assign routine number 2 | ||||||
1S | and 1 for internal references | ||||||
1 | 0 | 1 | D | SD | set D1=0 (D1 = D1 - D1) | ||
1 | 1 | 1A | 19 | M | C | set C=100000 limit for 5-digit numbers | |
1 | 2 | CA | D | D0 = A, A=0 | |||
1 | 3 | 15 | PE | PD | increment return address in D15 | ||
1 | 4 | PE | PS | skip next instruction | |||
1 | 5 | PE | PA | add PE (1024) into A | |||
1 | 6 | C | SD | D0 = D0 - C repeat subtract | |||
1 | 7 | SD | CS | if sign(D0) skip to leave loop | |||
1 | 8 | 1A | 5 | K | S | jump to 1,5 repeating subtracts | |
1 | 9 | C | PD | D0 = D0 + C | |||
1 | 10 | 1 | ZA | SD | D1 = D1 - (A==0?0:1) | ||
1 | 11 | 1 | SD | CS | if D1 < 0 skip | ||
1 | 12 | 31 | K | A | set A = space (31) | ||
1 | 13 | CA | OT | print A, clear A | |||
1 | 14 | 1A | 20 | M | XB | A,B = 0.1 * C | |
1 | 15 | ZA | CS | if A!=0 skip | |||
1 | 16 | 15 | D | S | return via D15 | ||
1 | 17 | CA | C | C = A, A = 0 | |||
1 | 18 | 1A | 6 | K | S | jump back to 1,6 | |
1 | 19 | 3 | 1 | HL | M | 3,1,21,0 = 100000 | |
1 | 20 | 1 | 19 | HA | L | 1,19,6,13 = 0.100000 | |
1S | re-assign routine number 1 for main routine | ||||||
1 | 21 | PS | T | halt to allow switches to be set | |||
1 | 22 | A | SA | clear the A register (A = A - A) | |||
1 | 23 | NA | C | read the NA switches into C | |||
1 | 24 | NB | XB | and multiply by the NB switches | |||
1 | 25 | B | A | Move the B register into A | |||
1 | 26 | HA | A | and divide by 2 | |||
1 | 27 | 29 | K | OT | print line feed | ||
1 | 28 | 30 | K | OT | print carriage return | ||
1 | 29 | 15 | S | D | copy S into D15 for return | ||
1 | 30 | 2A | K | S | jump into print subroutine | ||
1 | 31 | 1A | K | S | jump back to halt for another tr | ||
2 | 0 | 1A | D | K | S | trigger CONTROL to enter program at main routine |
The main routine reads the two switch registers and forms their product. It then moves the printer to a new line, then invokes the print subroutine.
The print subroutine (1,0 to 1,20) starts by setting C to 100,000 and the number to be printed int D0. Using a loop subtracting C, and incrementing A until D0 goes negative, it determines the number of times A is greater than 100,000. Having reached that point it adds back C into D0, and tests the value of A. If A is zero it substitutes a space character, then prints the value in A. For the printer, characters '0' to '9' have values 0 to 9, so this is easy. If it was a punch routine, for which the character set is different, it would be a bit more complex.
Having printed a digit, at (1,13), it then proceeds to multiply C by 0.10000, or 52429. Multiplication can be thought of in three ways - two integers, or two fractions, or one of each. The result can then be interpreted in three corresponding ways, a double length fraction, with the decimal point between P20 and P19 of A, or a double length integer with decimal point at P1 of B, or, in the case of a mixed product, in between A and B. The latter applies here, and the value in A is the integer after C has been divided by 10.
The code then tests if the resulting value in A is zero, in which case all 6 digits have been printed (or suppressed) and the subroutine returns to the main routine using D15. Otherwise, the value in A is transferred to C and the process repeats, printing subsequent digits.
The print routine has a bug! If the value to be printed is zero, then D1 never gets set and the final zero is suppressed along with all the rest. The result is just six spaces.
The tape file to run this program in included in the Emulator Download as Multiplication.cvt. In the original library it is listed as T732.CVT.
This is a simple program that varies x from 0.05 (0.05) 0.45 printing x and tan(x).
* T725 Demonstration Tan table | |||||||
* Headed table of x, angles 0(5 deg)45 deg and | |||||||
* corresponding value of tan(x) to 6 dec. places | |||||||
* Tan(x) = 0.785414 x | |||||||
* + 0.161198 x^3 | |||||||
* + 0.041414 x^5 | |||||||
* + 0.006624 x^7 | |||||||
* + 0.005348 x^9 | |||||||
* where x is a fraction of 90 deg | |||||||
* The above is what is typed on the tape header, but in fact | |||||||
* it is a headed table with x ranging 0.05(0.05)0.45 | |||||||
* i.e angles 4.5 deg(4.5 deg)40.5 deg and the corresponding | |||||||
* value of tan(x) to 6 dec. places | |||||||
1 | 2T | set load address = (1,2) = 34 | |||||
2S | subroutine to print a fraction is ref. 2 | ||||||
1S | set local base | ||||||
1 | 2 | SA | CS | test argument in A, skip if negative | |||
1 | 3 | 3 | K | PS | jump to (1,7) | ||
1 | 4 | CA | SA | A = 0 - A | |||
1 | 5 | 11 | K | OT | print '-' | ||
1 | 6 | PE | PS | skip next instruction | |||
1 | 7 | 10 | K | OT | punch '+' | ||
1 | 8 | 12 | K | OT | punch '.' | ||
1 | 9 | 1A | 17 | M | C | C = 10 | |
1 | 10 | 5 | K | D | D0 = 5*32 | ||
1 | 11 | CA | XB | Loop: A,B = A*10, A=most significant digit as an integer | |||
1 | 12 | CA | OT | print A, clear A | |||
1 | 13 | RB | A | A = fraction remainder | |||
1 | 14 | 1 | K | SD | D0=D0-1*32 | ||
1 | 15 | SD | CS | skip if D0 negative | |||
1 | 16 | 1A | 9 | K | S | loop back to Loop (1,11) | |
1 | 17 | 15 | PE | PD | increment return address | ||
1 | 18 | 15 | D | S | and return to caller | ||
1 | 19 | M | P | (0,0,0,10) = constant=10 | |||
3S | subroutine to calculate tan(x) | ||||||
1S | set local base address | ||||||
1 | 20 | A | PA | double the argument A=A+A | |||
1 | 21 | A | D | D = A | |||
1 | 22 | A | C | C = A | |||
1 | 23 | CA | XB | A,B = A * C | |||
1 | 24 | CA | C | C = A | |||
1 | 25 | 1A | 17 | M | XB | A,B = C * 0.004988 (2,5) | |
1 | 26 | 1A | 18 | M | PA | A = A + 0.006624 (2,6) | |
1 | 27 | CA | XB | A,B = A * C | |||
1 | 28 | 1A | 19 | M | PA | A = A + 0.041414 (2,7) | |
1 | 29 | CA | XB | A,B = A * C | |||
1 | 30 | 1A | 20 | M | PA | A = A + 0.161198 (2,8) | |
1 | 31 | CA | XB | A,B = A * C | |||
2 | 0 | 1A | 21 | M | PA | A = A + 0.0785418 (2,9) | |
2 | 1 | CA | C | C = A, A=0 | |||
2 | 2 | D | XB | A,B = D * C | |||
2 | 3 | 15 | PE | PD | increment return address in D15 | ||
2 | 4 | 15 | D | S | and return to caller | ||
2 | 5 | 2 | S | Z | (0,2,17,23) = 0.004988 (0.005?) | ||
2 | 6 | 3 | R | D | (0,3,12,17) = 0.006624 | ||
2 | 7 | 21 | HA | D | (0,21,6,17) = 0.041414 | ||
2 | 8 | 2 | 18 | D | OT | (2,18,17,2) = 0.161198 | |
2 | 9 | 12 | 18 | A | CA | (12,18,4,9) = 0.785418 | |
1S | main routine | ||||||
2 | 10 | 27 | K | OT | set printer to figure shift | ||
2 | 11 | 8 | 28 | K | HU | H = (8,28), 8 is count, | |
2 | 12 | 6 | HU | D | D6 = H * 1024 | ||
2 | 13 | 29 | K | OT | Loop: print line feed | ||
2 | 14 | 30 | K | OT | print carriage return | ||
2 | 15 | 6 | HU | SD | D6 = D6 - (H * 1024) | ||
2 | 16 | 1A | 25 | M | B | B = 0.050001 (3,3) | |
2 | 17 | 6 | B | PD | D6 = D6 + B | ||
2 | 18 | 6 | D | A | A = D6 | ||
2 | 19 | 15 | S | D | D15 = S (return address) | ||
2 | 20 | 2A | K | S | Jump to print routine 2 | ||
2 | 21 | 6 | D | A | A = D6 | ||
2 | 22 | 15 | S | D | D15 = S (return address) | ||
2 | 23 | 3A | K | S | Jump to Tan routine 3 | ||
2 | 24 | 31 | K | OT | print space | ||
2 | 25 | 31 | K | OT | ditto | ||
2 | 26 | 15 | S | D | D15 = S (return address) | ||
2 | 27 | 2A | K | S | Jump to print routine 2 | ||
2 | 28 | 6 | HU | PD | D6 = D6 + H*1024 | ||
2 | 29 | 6 | SD | CS | if D6 < 0 skip next instruction | ||
2 | 30 | 1A | 3 | K | S | loop back to Loop (2,13) | |
2 | 31 | 29 | K | OT | print line feed | ||
3 | 0 | 29 | K | OT | ditto | ||
3 | 1 | 31 | 31 | K | P | pulse speaker | |
3 | 2 | 31 | 30 | K | PS | loop back to continue hooting | |
3 | 3 | 25 | RD | CA | (0,25,19,7) = 0.050001 | ||
3 | 4 | 29 | K | OT | entry: print line feed | ||
3 | 5 | 29 | K | OT | and a second line feed | ||
3 | 6 | 30 | K | OT | print carriage return | ||
3 | 7 | 28 | K | OT | print letter shift | ||
3 | 8 | 31 | K | OT | print space | ||
3 | 9 | 31 | K | OT | ditto | ||
3 | 10 | 23 | K | OT | print 'X' | ||
3 | 11 | 31 | K | OT | print space | ||
3 | 12 | 31 | K | OT | ditto | ||
3 | 13 | 31 | K | OT | .. | ||
3 | 14 | 31 | K | OT | .. | ||
3 | 15 | 31 | K | OT | .. | ||
3 | 16 | 31 | K | OT | .. | ||
3 | 17 | 31 | K | OT | .. | ||
3 | 18 | 19 | K | OT | print 'T' | ||
3 | 19 | K | OT | print 'A' | |||
3 | 20 | 13 | K | OT | print 'N' | ||
3 | 21 | 31 | K | OT | print space | ||
3 | 22 | 23 | K | OT | print 'X' | ||
3 | 23 | 29 | K | OT | print line feed | ||
3 | 24 | 30 | K | OT | print carriage return | ||
3 | 25 | 1A | K | S | jump to start of main routine | ||
3 | 26 | 1A | 26D | K | S | trigger to enter at (3,4) |
This program consists of three routines, a print subroutine, a subroutine to calculate Tan(x), and the main routine, which cycles through the values of X, printing that value, calculating its tangent, and printing that.
The previous program was mostly working with integers, this one is mostly with fractions. Printing fractions is fairly easy, print the leading '0.' then loop around 6 times. Each time round multiply the fraction by 10, and the most significant digit ends up as an integer in A. As the printer maps 0..9 to '0'..'9' just print that value. Loop around until all six digits have been printed, and we're done. The constant (0,0,0,10) is would be simply punched as that, but the listings are generated from the 12-hole binary tape and it doesn't know that this is a constant, so it maps (0,10) to (M P).
The algorithm for generating Tan X is a sequence of multiplies and adds. I have never had to devise one of these routines, simply using a library function. I cannot comment on whether this is the best algorithm or not, but it is a splendid example of condensing the algorithm into a minimal sequence of orders. On entry the argument is in A, the return address is actually the address of the jump which invokes the subroutine, but by adding P11 (1024) to it, just before the return, it takes us back to the order following that jump.
The main routine is triggered to enter at (3,4) which is in the middle of the routine. The code following this simply prints out a title, prior to jumping in to the start of the routine to set up the loop over the values. A feature of interest is that the constant (0,25,19,7) is equivalent to the fraction 0.050001 which means the values are slightly out of spec. This is because it is not possible to specify 0.050000 in 20 bits. The alternative value produced by (0,25,19,6) comes out at 0.049999, which is equally in error. It is therefore a case of using the nearest approximation and someone decided that the slightly larger value would be the best choice in this instance.
Another interesting quirk is the use of (8,28) to initialise H with. The reason is that they wanted to use D0 as both a counter, and as the value of X. By judicious use of addition and subtraction, the 8 gets incremented as the counter, and when it reaches 16, the test at (2,29) tests it as negative, and skips out of the loop. By subtracting H from D0 at (2,15) it generates a value for X, which gets incremented at (2,17), then H is added back in at (2,28). It took me a while to work out what was going on there, and this seems to be a fairly typical trick to avoid using other registers or storage locations.
The program ends by dropping out of the loop with the test at (2,29), skipping to (2,31) where a further line feed and carriage return are printed, the program drops into an endless loop pulsing the loudspeaker, which prompts the operator to operate the stop switch.
The tape file to run this program in included in the Emulator Download as TangentTable.cvt. In the original library catalogue it is listed as T725.CVT.
The emulator written for Windows98, which is available online, came with four simple programs written for the Interprogram interpreter. I include these here, again courtesy of the University of Melbourne, and under the above license conditions. The files are included in my emulator download.
(1) TITLE ALPHA = BETA - 27.394 + A(3).(X/Y) bbbbb(2) SYMBOLS FOR INTEGERS J (3) MAXIMUM SUBSCRIPTS A(3) (4) COMPILE THE FOLLOWING INTERPROGRAM *1 INPUT J *2 IF THIS IS ZERO, GO TO *7 # END WHEN J = 0 *3 INPUT BETA, & A(3), & X, & Y *4 TAKE X, DIVIDE BY Y, MULTIPLY BY A(3) SUBTRACT 27.394, ADD BETA, REPLACE ALPHA TAKE J, OUTPUT, OUTPUT ALPHA *6 GO TO *1 *7 END OF INTERPROGRAM 1 25.382 2.754 7 0.92 2 31.542 -1.832 9 1.24 3 22.379 -0.023 11 2.57 4 23.456 +0.001 23 0.17 5 27.356 +1.113 3 -95.76 0 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
This program reads in lines of five numbers, J, BETA, A(3), X and Y. J is simply an index, at J = 0 acts to terminate the input. The program, as stated in the first line, calculates ALPHA = BETA - 27.394 + A(3).(X/Y), and prints out J and ALPHA, then returns to read the next line.
The use of lower case 'b' is because Interprogram makes use of blank tape as part of the specification, and the emulator needs to be able to provide the relevant value as an input character to enable the interpreter to work correctly.
The first line: TITLE... requires blank tape to terminate the arbitrary string of text, and owing to the way it was written, you have to put several blanks, in this case the title include the end of line marker (carriage return for the Flexowriter, which ignores line feeds!).
Line (2) defines J to be integer, any other variables are real values. I find it interesting that the early autocodes tended to have very basic identifiers, whereas Interprogram allows fairly generous freedom (though it ignores any characters after the fourth.) Line (3) is optional and can be omitted if there are no arrays. Line (4) is required and the program will fail if it is omitted. Actually all that is needed is the last four characters 'GRAM'.
The program proper follows, with most of them being labelled in this example, although only *1 and *7 are referenced. Each statement begins with a command word, followed by a variable or constant, the use of ',' separates multiple statements on the same line, and '&' is taken to mean use the previous command again. There is a pseudo variable 'THIS' which means the result of the previously computed value. The command TAKE, sets THIS to the value specified, and the subsequent commands imply the use of THIS value to operate with the given value. OUTPUT, on its own, means 'OUTPUT THIS'.
Usual practice was to append data to the program file, as seen here, though the system would read in the program and pause to allow the operator to load a different tape if required.
(1) TITLE INTEREST CALC bbbbb(2) SYMBOLS FOR INTEGERS J (4) COMPILE THE FOLLOWING INTERPROGRAM *1 INPUT J, & X *2 TAKE J, DIVIDE BY 100 #SCALED - RULE 3 *3 MULTIPLY BY X, REPLACE INTEREST #SCALED - RULES 2(B), 1 *4 MULTIPLY BY 20, ADD .0001, REPLACE INTEREST, REPLACE J #INTEGER-RULE 1 OUTPUT, SUBTRACT INTEREST #-VE FRACTION OF SHILLING MULTIPLY BY -12, REPLACE J, OUTPUT #THIS = INTEGER PENCE *7 END OF INTERPROGRAM eeeee 5 12.0000 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
In this program line *2 shows an integer being converted to real (5 -> 0.05), then conversion back to integers to get shillings and pence in lines 4 and 6. For those who post-date decimal currency, I should explain that these sort of calculations were common-place with the old money!
(1) TITLE LOG(X+IY)=U+IV WHERE U=LOG(SQRT(X*X+Y*Y)) & V=ARCTAN Y/X bbbbb(2) SYMBOLS FOR INTEGERS NONE (4) COMPILE THE FOLLOWING INTERPROGRAM *1 INPUT X, & Y *2 TAKE X, MULTIPLY BY X, REPLACE XSQUARED *3 TAKE Y, MULTIPLY BY Y, ADD XSQUARED *4 FORM SQUARE ROOT, FORM NATURAL LOG, REPLACE U TAKE Y, DIVIDE BY X, FORM ARCTAN IF X IS POSITIVE, GO TO *37 # EXTEND ARCTAN ADD 1, IF THIS IS GREATER THAN 1, ADD -2 # TO RANGE (-1,1) *37 MULTIPLY BY 3.14159, REPLACE V # V IN RADIANS OUTPUT U, & V *40 END OF INTERPROGRAM eeeee 2 4 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Modern programming languages provide constructs for calculating with complex numbers, but in the early days it all had to be explicitly coded. Use of conditional statement (IF) is illustrated on lines 6 and 7 (unlabelled)
(1) TITLE EXAMPLE OF OUTPUT, LAYOUT AND FUNCTION FORMATION bbbbb(2) SYMBOLS FOR INTEGERS NONE (4) COMPILE THE FOLLOWING INTERPROGRAM PUNCH THE FOLLOWING CHARACTERS DATUM LOG EXP(LOG) SINE TAN ARCTAN(TAN) ------------------------------------------------------------l bbbbbbCOPY TAPE # DATA TAPE IDENTIFICATION *2 INPUT DATUM, GO TO *3, GO TO *4 #WHEN 'END' IS READ *3 OUTPUT, FORM NATURAL LOG, OUTPUT, FORM EXPONENTIAL, OUTPUT, # EXP(LOG(DATUM)) TAKE DATUM, FORM SINE, OUTPUT, TAKE DATUM, FORM TANGENT, OUTPUT, FORM ARCTAN, OUTPUT # END PRINTED ROW GO TO *2 # REPEAT FOR EACH DATUM VALUE *4 END OF INTERPROGRAM eeeeeSAMPLE SET OF DATA bbbbbee0.0 0.166667 0.25 0.333333 0.5 0.666667 1.0 2.71828 -0.166667 -0.25 -0.5 123.456 -4567.89 END bbbbbbbbbbb
This routine makes use of the PUNCH command to copy headings to the output. As with TITLE, the text is terminated with blank tape. The COPY TAPE command reads some text from the data tape (in this case the text is on the same tape, following the program), again terminated by blanks. I am unclear about the use of the character 'e' - as far as either of the emulators are concerned it is completed ignored!
The data consists of a series of numbers, read in as DATUM, each number is taken as the argument to a number of standard functions. Note also the use of optional returns from the INPUT command, where 'END' is recognised as a terminator.