Sharper Focus on APL
Vector 3:1, page 79
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 79
Introduction¶
There is a faith and a hope, often expressed in the data processing and lay press, that it will only be a short time before software is written which will let computers be instructed in plain English, and that this will allow end users to interact directly with their machines.
This faith is mistaken and this hope is misplaced. The faith is mistaken because it will be a very long time before computers are able to interpret plain English with anything like the adequacy of a child of five and the hope is misplaced because, even were computers to have the linguistic ability of Dr. Johnson himself, plain English would be an inappropriate language in which to address them – at least for commercial purposes.
What follows is an example of a normal commercial purpose, used to illustrate and justify the above heresy and to put the case for more appropriate, immediately available and cheaper software. It is argued that most software currently used is written in code which is long on bastard English and short on logical and mathematical notation. This is a reflection of the simplistic approach adopted by the first language writers who worked within the constraints of tape and punched cards and minimal storage. Their world was “just one damn thing after another” and most languages have moved ever since with the tottering gait imposed by their bound feet. Hardware is now cheap – the bindings have gone. It is time we learned to leap.
A taxing problem¶
Consider the following specification for the calculation of Head Office charges:
“Head Office Charge is a percentage of a Store’s annual turnover. The percentage varies with the bands into which the turnover falls. For example, the current bands and percentages for Grocery are:
From To % 0 5000 5 5000 8000 2.5 8000 15000 1 15000 30000 0.5 30000 - 0.1 ”
There is nothing unusual about this requirement; accountants and analysts are expected to make, or to provide programs to make, such calculations every day of the week. So what is there to say about this specification and the ways it might be met?
Firstly it is ambiguous because it does not make clear which of two common methods of using bands (or ranges) is meant. Think of the boxes that are so favoured for questionnaires:
Please Indicate age band:
0-5 6-10 11-18 19-25 26-35 36-45 46-55 56-65 65+
It is quite clear that you must tick only one box. If you are between 26 and 35, then you cannot simultaneously be between 19 and 25.
Next, however painful, think of income tax. Here is the table:
| 1-16200 | 30% |
| 16201-19200 | 40% |
| 19201-24400 | 45% |
| 24401-32300 | 50% |
| 32301-40200 | 55% |
| 40201+ | 60% |
Suppose your income is £16500; will you pay 40% on all of it? Fortunately not, or no one would willingly earn between £16200 and £18901 (take-home pay at 70% of £16200 equals 60% of £18901). We all know that the real tax calculation is:
(.3 × 16200) + ((.4 × (16500 – 16200))
i.e. that successive slices of income are charged at different rates. In other words more than one ‘box’ may be ticked.
Head Office Charge, in this case, is meant to be treated like Income Tax – different rates on successive slices of turnover – although, unlike Income Tax, it is regressive (the rate reduces). An accountant would have no difficulty; common sense would tell him to use the second method of calculation.
Programming common sense into computers is another matter entirely and to date common sense languages cannot be bought. So, even if we were able to feed this specification directly into the computer, its action would not be adequately determined.
So how must we write the specification to ensure that it is not misunderstood? How about:
“H.O.C. is the sum of n(1)% of the first £m(1) of annual turnover, n(2)% of the next £m(2) of annual turnover, . . . and so on. The current values for Grocery turnover are:
m n 5000 5 3000 2.5 7000 1 15000 0.5 rest 0.1 ”
Do any of us suppose that this is sufficiently precise for a computer? Probably no one of us is that rash. From which I conclude that if, by some miracle, we had a 4th or 5th generation language which could interpret plain English, we should find the plain (to a computer) English very hard to write and painful to read, because to be precise we should have to write English which is far from plain. The English used in statute law is a good example of what happens when people try to make it precise and unambiguous.
In any case, we cannot wait for miracles; the job requires that we choose some language in which to instruct the computer to make the H.O.C. calculation. What choices do we have? How much code must we write and are the facilities offered by the language sufficient for the job to be cheap to set up and easy to maintain (change)?
While a spread-sheet type of code (Lotus 1-2-3 or Wizard) will look different from a file manipulation type (dBASE II or FOCUS), we can for any of them summarize the job into three steps:
- find out which band this turnover falls into
- sum the H.O.C. on all lower bands
- add the amount due on the remainder.
As it is a small but instructive exercise, I suggest that those among my readers who are familiar with any computer language consider how to code the H.O.C. Of course there is no right way to do it – there are many ways of skinning a cat – but it will be helpful as a particular example of the general charcteristics of code which are discussed below.
The spreadsheet approach¶
All computer languages are able to do simple arithmetic. They can add, subtract, multiply, divide, raise to a power and usually extract a square root. Therefore working out a maximum levy on each band of turnover will present no problem and it will follow closely the steps an accountant would take if using a manual spread-sheet. He will tend to use additional columns in the spread-sheet to hold his intermediate workings, thus:
| COL1 FROM |
COL2 TO |
COL3 % |
COL4 TO-FROM |
COL5 CHARGE |
COL6 |
|---|---|---|---|---|---|
| 0 | 5000 | 5 | 5000 | 250 | / |
| 5000 | 8000 | 2.5 | 3000 | 75 | / |
| 8000 | 15000 | 1 | 7000 | 20 | |
| 15000 | 30000 | 0.5 | 15000 | - | |
| 30000 | - | 0.1 | - | - | |
| 345 |
A program would probably not need COL4. The code for COL5 would be simple:
But of course we do not necessarily want all the values in COL5, as that will depend on the turnover which is a variable. The accountant working manually has no problem; each time he re-works he knows what the turnover is and chooses accordingly. Suppose the turnover is 10000. He looks down COL2 and says to himself “is 10000 greater than COL2” and mentally ticks that row if the answer is yes (see COL6). Then the first row of COL6 which has no tick is the one which will hold the calculation for the remainder of the charge:
i.e. 20. So the total charge is 250 + 75 + 20 = 345. The accountant has used mental ticks to choose the rows, but what is the program equivalent of a tick?
Some packages may be really clever. They may allow the turnover to be tested against a table of ranges without explicit coding. Then the programmer will provide a remainder calculation something like:
COL5 ROW(RANGE=TURNOVER) = (COL2 ROW(RANGE=TURNOVER)-COL1 ROW(RANGE=TURNOVER))
* COL3 ROW(RANGE=TURNOVER)/100
and the sum of the charge rows would be:
Less sophisticated software would require a set of tests like:
FOR ROWS 1 TO 5
IF COL2>TURNOVER COL5=(TURNOVER-COL1)*COL3/100 GOTO LABEL
OTHERWISE COL5=(COL2-COL1)*COL3/100
NEXT ROW
LABEL COL5 ROW6 = SUM COL5 ROWS 1 THRU 5
All programmers will recognize this old friend. It is the loop and can be a lot less friendly than the RANGE type of code especially if it is part of a nest of loops which all too easily turn into a nest of spaghetti.
So why cannot software have more friendly features like the range test? In principle there is no reason why not and most languages allow subroutines to be called from public libraries to perform frequently-needed calculations. In practice the reason is “where do we stop?”. There will be as many friendly features as there are jobs to be done, and the users are bound to want a variation on the theme that no one has thought to provide.
Expert systems are one way to reduce this problem – they acknowledge that the software has been written for a special purpose and build in particular features, ignoring the general cases. This is an admirable solution for areas where the job to be done is well understood and widespread. The two most successful examples are word-processing and conventional spreadsheet accounting.
Suppose however that your job is not run-of-the-mill. What then will be the language of your choice?
A mathematical approach¶
The reader will have noticed that the pseudo-code used for illustration contains as many arithmetical symbols as words. Also that the problem centres around the tests (ticks in COL6), which if they are explicit lead to looping code that is difficult to maintain. The code is a clumsy mixture of English or near-English words and a few mathematical symbols, some of which (* for multiply and / for divide) are not the conventional ones that everybody learnt at school.
Clearly more English does not answer the problem so how about using more mathematics?
Commercial users will respond that they do not need mathematics because “all we do is a bit of simple arithmetic – working out a few percentages – that sort of thing”. This is true if we attend only to what the program does and no attention is paid to how it does it.
Think of the one bit of mathematics we can probably all remember: (Ed: the name at least!) Pythagoras’ Theorem. Somewhere at the beginning of that page in the textbook there will be the phrase “for any right triangle . . .” For any particular triangle, it is easy enough to show by measuring that the square on the hypoteneuse equals the sum of the squares on the other two sides, but Pythagoras said it is true of any right triangle and that is an altogether different grandmother (maybe as powerful as a wolf?).
Programs resemble wolves in this respect – they should be able to digest any small girl whatever the colour of her hood. That is, they should provide consistent results whatever the data input, after themselves checking that it is well-formed (that is valid, or girl-shaped). Programs are only written because there are many occasions on which the same algorithm is to be applied to different data. In the commercial world, one-off jobs are done more cheaply by hand or with a calculator; only horrendously difficult calculations would warrant writing a program to be run just once.
There is a word to describe this type of computation – it is said to be ‘generalized’ – and the equivalent mathematical discipline is algebra. In the example above, turnover is a variable, the values in the table are constants, the result is the Head Office Charge and the program is the formula relating them. The difference between the program and ordinary algebra is that a mathematician does not have to describe how to choose the band into which the turnover happens to fall because he is describing the relationship for the benefit of another person and he can assume that the right band will be chosen by “simple inspection”. The programmer must substitute an algebra of “ticks” or “true”/“false” for “simple inspection”. Such an algebra was developed in the last century by George Boole, but its implementation in most languages is crude.
Of course, a programming language will also need to manipulate text. It must allow tests like DIVISION=‘GROCERY’, but much more power than this is desirable. Sorting is an obvious example but there are many others. Given two lists of names, are all items in list A also contained in list B, and if so where? Which elements of A are not in B? Is every element of list A unique? Left/right justify these lists or centre them. Extract subsets and join them . . . and so on.
Assume that we have a language which has a full range of algebraic and Boolean functions and text manipulation. What would the code look like that does the H.O.C. job, and what advantages would it have? Here is an example:
R←TURN HOC TABLE;BOO;ROW;FROM;TO;PC
⍝ HOW TO USE LOGIC TO AVOID LOOPS
FROM TO PC←↓[1]TABLE
BOO←TURN≥TO ⋄ ROW←1++/BOO
R←+/(BOO×(TO-FROM)×PC÷100),(TURN-FROM[ROW])×PC[ROW]÷100
The reduction in quantity of code is small (this has 129 characters as against 136 for the pseudo-code given previously) but look more closely at that code, especially the first line which reads ‘FOR ROWS 1 TO 5’. Suppose the number of turnover bands in the table changes. Then the code must be changed to limit the loop to the number of rows in the table. Now look at the new version. The number of rows is nowhere mentioned. It does not need to be mentioned because the code is generalized – it will work with a table of any size.
Here at last is the answer to the problem which data processing managers have spent years looking for – how to avoid maintenance costs and allow users to change the shape of their data.
To see the code at work, we can first look at the contents of the variable and the table of constants:
and then switch on a trace to display the result calculated on each line:
TURN HOC TABLE
HOC[1]
HOC[2] (0 500 1200 2000 3000)(500 1200 2000 3000 999999)(4 2.5 1.5 1 .5)
HOC[3] 1 1 0 0 0
⋄ 3
HOC[4] 42
and run the code:
and HOC (Head Office Charge), the result (pure chance, I swear) is 42 . . . the answer to the ultimate question about Life, the Universe and Everything!
The action of the code traced was as follows:
- line 1:
- comment only
- line 2:
- split the table into its columns and call them FROM, TO, PC
- line 3:
- create a Boolean variable by comparing the turnover, TURN, with column TO (1500 is greater than 500 & 1200, but less than 2000, 3000 & 999999, so BOO becomes 1 1 0 0 0); the second part of line 3 calculates the number of the remainder row by adding 1 to the sum of the Boolean variable, thus creating the variable ROW.
- line 4:
- uses these intermediate results to sum the remainder (the part after the comma) with the levy on all the rows multiplied by the Boolean which, naturally, reduces those rows corresponding to the Boolean value 0 to zero, while those corresponding to the Boolean value 1 are left unchanged. The Boolean is a mask selecting only those rows where the answer to the question was “yes” – i.e. true or ticked.
To demonstrate that the size of the table is of no consequence, we can run the code with a much larger table – BIGTABLE has 100 rows.
TURNS←550 1300 2000
ALLHOCS[1]
ALLHOCS[2] (0 500 1200 2000 3000)(500 1200 2000 3000 999999)(4 2.5 1.5 1 .5)
ALLHOCS[3]
1 0 0 0 0
1 1 0 0 0
1 1 1 0 0
⋄ 2 3 4
ALLHOCS[4] 21.25 39 49.5
Some serious generalization.¶
HOC takes a single turnover value and returns the Head Office Charge, but the program of which this is a part will be making the same calculation for many turnover values – perhaps for each department in a store or over successive years for a discounted cash flow. Must we write looping code or can we do all calculations at the same time?
Here is the code to calculate the H.O.C. on any number of turnover values. The code is still 4 lines long, but there is a rather less familiar notation:
- ¨
- means ‘each’;
- ∘.≥
- means create a table of Boolean values instead of the vector we had before;
- ⊂
- means treat what follows as a single object.
R←TURNS ALLHOCS TABLE;BOO;ROWS;FROM;TO;PC
⍝ HOW TO USE NESTED ARRAYS TO DO MORE AT A TIME
FROM TO PC←↓⍉TABLE
BOO←TURNS∘.≥TO ⋄ ROWS←1++/BOO
R←(+/↑((⊂((TO-FROM)×PC÷100))ר↓BOO))+(TURNS-FROM[ROWS])×PC[ROWS]÷100
To see how this works, here is the trace for only 3 turnover values, so that the size of the Boolean array stays small enough for us to view at a glance. (It has one row for each value in TURNS.
TURNS←550 1300 2000
ALLHOCS[1]
ALLHOCS[2] (0 500 1200 2000 3000)(500 1200 2000 3000 999999)(4 2.5 1.5 1 .5)
ALLHOCS[3]
1 0 0 0 0
1 1 0 0 0
1 1 1 0 0
⋄ 2 3 4
ALLHOCS[4] 21.25 39 49.5
and here are the three charges: 21.25 39 49.5
However, to see the power of this generalization, we can use a large table of turnovers for 14 departments by 8 years:
TURNOVER
238704 298380 340153 367365 385734 405020 425271 446535
38972 48715 55535 59978 62977 66126 69432 72904
25900 32375 36908 39860 41853 43946 46143 48450
23178 28973 33029 35671 37454 39327 41294 43358
0 0 0 0 0 0 0 0
10423 13029 14853 16041 16843 17685 18569 19498
238807 298483 340256 367468 385837 405123 425374 446638
39075 48818 55638 60081 63080 66229 69535 73007
26003 32478 37011 39963 41956 44049 46246 48553
23281 29076 33132 35774 37557 39430 41397 43461
738704 298380 340153 367365 385734 405020 425271 446535
10526 13132 14956 16144 16946 17788 18672 19601
38972 48715 55535 55978 62977 66126 69432 72904
15846 19808 22582 24387 25606 26887 28231 29642
TURNOVER ALLHOCS TABLE
1238.02 1536.4 1745.265 1881.325 1973.17 2069.6 2170.855 2277.175
239.36 288.075 322.175 344.39 359.385 375.13 391.66 409.02
174 206.375 229.04 243.8 253.765 264.23 275.215 286.75
160.39 189.365 209.645 222.855 231.77 241.135 250.97 261.29
0 0 0 0 0 0 0 0
96.615 109.645 118.765 124.705 128.715 132.925 137.345 141.99
1238.535 1536.915 1745.78 1881.84 1973.685 2070.115 2171.37 2277.69
239.875 288.59 322.69 344.905 359.9 375.645 392.175 409.535
174.515 206.89 229.555 244.315 254.28 264.745 275.73 287.265
160.905 189.88 210.16 223.37 232.285 241.65 251.485 261.805
3738.02 1536.4 1745.265 1881.325 1973.17 2069.6 2170.855 2277.175
97.13 110.16 119.28 125.22 129.23 133.44 137.86 142.505
239.36 288.075 322.175 324.39 359.385 375.13 391.66 409.02
123.73 143.54 157.405 166.435 172.53 178.935 185.655 192.71
Summary¶
The purpose of this paper has been twofold. First to persuade the reader that the search for a programming language which can accept plain English will be fruitless. Secondly to demonstrate that the effect of applying a language with the combined power of algebra and logic to a commercial problem is to create concise code which will tolerate major changes in the format of the data offered to it. This has the effect of accommodating change without incurring additional programming costs.
Of course, an intellectual investment must be made in learning the concepts behind the language, but while this will be true whatever software is chosen, there is another factor to be taken into account:
Mathematics is not likely to go out of fashion
whereas we have all seen VISICALC rise and fall in the space of a few years, and the upstart relational database challenge the hierarchical champion.
Whenever new fields in science and technology are opened up, there is a spate of not very well co-ordinated activity as its potential is explored, but this is followed by a more sober consolidation. The consolidation is almost always mediated by the development of mathematical tools to fit the application of the new technology. For the sort of software considered here, this tool is already available and certainly no more difficult to use than the code it replaces.
As illustration, I attach code taken from the FOCUS User’s Manual. The purpose of the code is to allow FOCUS to call a subroutine which enables it to translate a month number into a month name (1 becomes ‘JANUARY’, 2 ‘FEBRUARY’, etc.). Here is an example of FOCUS code which uses it:
FOCUS User’s Manual — User written subroutines
Example of running MTHNAM
>>DEFINE
>MONTH/I2 = RCOST/1000 ;
>MONTH_NAME/A9 = MTHNAM(MONTH,MONTH_NAME) ;
>END
>>TABLE FILE CAR
>PRINT RCOST MONTH MONTH_NAME
>END
The manual supplies a choice of code to do this translation in ASSEMBLER, FORTRAN, PL/1 and COBOL (see Appendices 1 to 4). Here is the equivalent in the language of my choice:
R←MTHNAME MTH;⎕IO;NUM
⍝ TRANSLATE MONTH NUMBER INTO MONTH NAME
NUM←MTH ⍝ KEEP ORIGINAL NUMBER EVEN IF ERROR
⎕IO←1 ⋄ MTH←(MTH∊⍳12)×MTH ⋄ ⎕IO←0 ⍝ ZERO TO ERRORS
R←(13 10 ⍴'ERROR JANUARY FEBRUARY MARCH APRIL MAY JUNE JULY AUGUST SEPTEMBER OCTOBER NOVEMBER DECEMBER ')[MTH;]
R←(⍕NUM),' ',R
MTHNAME 1
1 JANUARY
But it would be much better if the function could be generalized in such a way as to make it unnecessary to specify how many numbers there are without needing to write a loop. With a little more ‘mathematical’ notation it can be done; the following function takes a list of any length.
R←MONTHNAMES MTHS;⎕IO
⍝ TRANSLATE MONTH NUMBER INTO MONTH NAME
⎕IO←1
MTHS←1+(MTHS∊⍳12)×MTHS
R←(13 14 ⍴' ERROR 1 JANUARY 2 FEBRUARY 3 MARCH 4 APRIL 5 MAY 6 JUNE 7 JULY 8 AUGUST 9 SEPTEMBER 10 OCTOBER 11 NOVEMBER 12 DECEMBER ')[MTHS;]
R←((1↑⍴R),×/1↓⍴R)⍴R
MONTHNAMES 1 2 4
1 JANUARY
2 FEBRUARY
4 APRIL
But why limit the input to lists? There is no reason why a truly general function should not process a table (a list of lists). Let us follow Pythagoras and make the leap –
“for any array of numbers . . .”
NUMBERS←10 4 ⍴⍳13
NUMBERS
1 2 3 4
5 6 7 8
9 10 11 12
13 1 2 3
4 5 6 7
8 9 10 11
12 13 1 2
3 4 5 6
7 8 9 10
11 12 13 1
MONTHNAMES NUMBERS
1 JANUARY 2 FEBRUARY 3 MARCH 4 APRIL
5 MAY 6 JUNE 7 JULY 8 AUGUST
9 SEPTEMBER 10 OCTOBER 11 NOVEMBER 12 DECEMBER
ERROR 1 JANUARY 2 FEBRUARY 3 MARCH
4 APRIL 5 MAY 6 JUNE 7 JULY
8 AUGUST 9 SEPTEMBER 10 OCTOBER 11 NOVEMBER
12 DECEMBER ERROR 1 JANUARY 2 FEBRUARY
3 MARCH 4 APRIL 5 MAY 6 JUNE
7 JULY 8 AUGUST 9 SEPTEMBER 10 OCTOBER
11 NOVEMBER 12 DECEMBER ERROR 1 JANUARY
Appendix 1. ASSEMBLER User-written Subroutine MTHNAM (FOCUS User’s Manual)¶
MTHNAM START 0
STM 14,12,12(13) save registers
BALR 12,0 load base registers
USING *,12
*
L 3,0(0,1) load addr of first arg into R3
LD 4,=D'0.0' clear out FPR4 and FPR5
LE 6,0(0,3) FP number in FPR6
LPER 4,6 abs value in FPR4
AW 4, =D'0.000001' add rounding constant
AW 4,DZERO shift out fraction
STD 4,FPNUM move to memory
L 2,FPNUM+4 integer part in R2
TM 0(3),B'10000000' check sign of original number
BNO POS branch if positive
LCR 2,2 complementr if positive
*
POS LR 3,2 copy month number into R3
C 3,=F'0' is it zero or less?
BNP INVALID yes, so invalid
C 3,F='12' is it greater than 12?
BNP VALID no, so valid
*
INVALID LA 3,13(0,0) bad, set R3 to item 13
*
VALID SR 2,2 clear out R2
M 2,=F'9' multiply by shift in table
LA 6,MTH(3) get addr of item in R6
L 4,4(0,1) get addr of second item in R4
MVC 0,(9,4),0(6) move in text
*
LM 14,12,12(13) recover regs
BR 14 return
*
DS 0D alignment
FPNUM DS D floating-point number
DZERO DC X'4E00000000000000' shift constant
MTH DC CL9'dummyitem' month table
DC CL9'JANUARY'
DC CL9'FEBRUARY'
DC CL9'MARCH'
DC CL9'APRIL'
DC CL9'MAY'
DC CL9'JUNE'
DC CL9'JULY'
DC CL9'AUGUST'
DC CL9'SEPTEMBER'
DC CL9'OCTOBER'
DC CL9'NOVEMBER'
DC CL9'DECEMBER'
DC CL9'**ERROR**'
END MTHNAM
Appendix 2. PL/1 User-written Subroutine MTHNAME (FOCUS User’s Manual)¶
MTHNAM: PROC(MTH,MONTH) OPTIONS(COBOL);
DECLARE MTH DECIMAL FLOAT (16) ;
DECLARE MONTH CHARACTER (9) ;
DECLARE IMTH FIXED BIN (15,0) STATIC;
DECLARE MONTH-TABLE(13) CHARACTER (9) STATIC
INIT ('JANUARY',
'FEBRUARY',
'MARCH',
'APRIL',
'MAY',
'JUNE',
'JULY',
'AUGUST',
'SEPTEMBER',
'OCTOBER',
'NOVEMBER',
'DECEMBER',
'**ERROR**) '
IMTH = MTH + 0.000001;
IF IMTH < 1 | IMTH > 12 THEN
IMTH = 13 ;
MONTH = MONTH_TABLE(IMTH);
RETURN;
END MTHNAME;
Appendix 3. FORTRAN User-written Subroutine MTHNAM (FOCUS User’s Manual)¶
SUBROUTINE MTHNAM (MTH,MONTH)
REAL*8 MTH
INTEGER*4 MONTH(3),A(12,3),IMTH
DATA
+ A( 1,1)/'JANU'/, A( 1,2)/'ARY '/, A( 1,3)/' '/,
+ A( 2,1)/'FEBR'/, A( 2,2)/'AUARY/, A( 1,3)/' '/,
+ A( 3,1)/'MARC'/, A( 3,2)/'H '/, A( 1,3)/' '/,
+ A( 4,1)/'APRI'/, A( 4,2)/'L '/, A( 1,3)/' '/,
+ A( 5,1)/'MAY '/, A( 5,2)/' '/, A( 1,3)/' '/,
+ A( 6,1)/'JUNE'/, A( 6,2)/' '/, A( 1,3)/' '/,
+ A( 7,1)/'JULY'/, A( 7,2)/' '/, A( 1,3)/' '/,
+ A( 8,1)/'AUGU'/, A( 8,2)/'ST '/, A( 1,3)/' '/,
+ A( 9,1)/'SEPT'/, A( 9,2)/'EMBE'/, A( 1,3)/'R '/,
+ A(10,1)/'OCTO'/, A(10,2)/'BER '/, A( 1,3)/' '/,
+ A(11,1)/'NOVE'/, A(11,2)/'MBER'/, A( 1,3)/' '/,
+ A(12,1)/'DECE'/, A(12,2)/'MBER'/, A( 1,3)/' '/,
+ A(13,1)/'**ER'/, A(13,2)/'ROR*'/, A( 1,3)/'* '/,
IMTH=MTH+0.000001
IF (IMTH.LT.1.OR.IMTH.GT.12) IMTH=13
DO 1 I=1,3
1 MONTH(I)=A(IMTH,I)
RETURN
END
Appendix 4. COBOL User-written Subroutine MTHNAM (FOCUS User’s Manual)¶
ID DIVISION
PROGRAM-ID. MTHNAM.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. IBM-370.
OBJECT-COMPUTER. IBM-370.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 MONTH-TABLE.
05 FILLER PIC X(9) VALUE 'JANUARY '.
05 FILLER PIC X(9) VALUE 'FEBRUARY '.
05 FILLER PIC X(9) VALUE 'MARCH '.
05 FILLER PIC X(9) VALUE 'APRIL '.
05 FILLER PIC X(9) VALUE 'MAY '.
05 FILLER PIC X(9) VALUE 'JUNE '.
05 FILLER PIC X(9) VALUE 'JULY '.
05 FILLER PIC X(9) VALUE 'AUGUST '.
05 FILLER PIC X(9) VALUE 'SEPTEMBER'.
05 FILLER PIC X(9) VALUE 'OCTOBER '.
05 FILLER PIC X(9) VALUE 'NOVEMBER '.
05 FILLER PIC X(9) VALUE 'DECEMBER '.
05 FILLER PIC X(9) VALUE '**ERROR**'.
01 MLIST REDEFINES MONTH-TABLE.
05 MLINE OCCURS 13 TIMES INDEXED BY IX.
10 A PIC X(9).
01 IMTH PIC S9(5) COMP.
LINKAGE SECTION.
01 MTH COMP-2
01 MONTH PIC X(9).
PROCEDURE DIVISION USING MTH, MONTH.
BEG-1.
ADD 0.000001 TO MTH.
MOVE MTH TO IMTH.
IF IMTH<+1 OR>12
SET IX TO +13
ELSE
SET IX TO IMTH.
MOVE A (IX) TO MONTH.
GOBACK.