Few if any computers included floating point before 1955 and yet
there were complex physics programs by that time.
Here is a note about the practice of fixed point approximations to real numbers.
This goes as well for other sciences but I think physics was the pioneer user of
the early computers.
The IBM 701 took after the Princeton machine in many of these details,
as did the ERA 1103; The 701 and 1103 were 36 bit machines.
The Princeton machine was 40 bits.
The IBM 701 had two 36 bit programmable registers,
the AC and the MQ.
(Excluding the program counter.)
The machine was sign and magnitude; the left most bit was 1 for negative numbers
and inverting that bit of the representation of x yielded
the representation of –x.
Add and subtract were like modern two’s complement machines unless negative
numbers were involved.
The multiply instruction left the 72 bit product of memory and the MQ value,
in the combined AC and MQ.
A long shift instruction operated on the combined registers to shift
the resulting double number to adjust for scaling and put
the result in either the AC or MQ.
The divide instruction left the quotient of AC-MQ in the MQ.
The remainder was left in the AC.
These shift commands did not change the sign.
Compilers were scarce and did not support these operations well.
Coding practice was as if there were these C functions:-
int36 mul(int36 x, int36 y, int sh){int72 p = (int72)x*y; return p>>sh;}
- int36 div(int36 n, int36 d, int sh){return ((int72) n << sh) / d;}
Or in more mathematical notation the functions are:
-
m(x, y, s) = IntegerPart(xy(2–s))
- d(x, y, s) = IntegerPart((x2s)/y)
In these expressions the arguments and function values are interpreted as signed integers with magnitude less than 235.
The value for s is constant for a given expression and comes from the instruction stream.
The shift amount was seldom modified during program execution.
The multiply could be carried out by machine instruction such as:
- Load MQ from location x;
- Multiply by location y;
- Long shift right by s bits;
- Store MQ in location of answer.
Note that this code does not check that the product fits in the one word field.
Divide could be done as:
- Load AC from location x;
- Long shift right (36–s) bits;
- Divide by location y;
- Store MQ in answer location.
The 701 would stop with the “divide check” indicator lamp on if the quotient didn’t fit in the MQ.
Global Scaling
Of course when doing physics these bit patterns are not interpreted as integers.
Each variable is given its own scaling as a power of two; another level of interpretation.
Each variable required a pre compile decision about scaling.
Typically the decision was tantamount to assertions such as:
|x| < 29.
The exponent of 2 might well be negative.
It was common that |x| < 29 and |y| < 2–3 but also that
|xy| < 24.
In this case the last inequality does not follow from the first two.
This is a common situation in physics calculations for the two factors are correlated and do not attain their max at the same time.
Such assertions were often wishful thinking that could be violated by unexpected physical situations.
Discovering easily that the assertion was violated required extra code that was not always included.
When scaling decisions were violated it was necessary to modify all references to the variable.
Scaling of subexpressions might also require application insight.
This was a particular obstacle in designing a good language for these machines.
A testable one bit indicator called “overflow” would be turned on when an add or subtract result exceeded the range of fixed point values.
A long left shift would also turn on this indicator(?).
Occasional testing of overflow would alert that scaling assumptions had not been met.
Interpretive Floating Point
Speedcode was a 701 program that provided what might be called today a “virtual machine” with floating point and index registers.
John Bakus, later of Fortran fame, pioneered this system.
I did not use this system.
Modern Machines
Today’s machines do not support this sort of arithmetic very well.
Floating point has displaced this niche.
Scaled arithmetic requires a product that takes one word from somewhere in the double word product.
Scaled divide is likewise difficult.
Today most machines make such calculations awkward.
C and other languages do not express the task well either.
Digital Signal Processors retain some of this arithmetic flexibility but the few that I have looked at are not as general purpose as the 701 in this regard.
Apple’s graphics software on the 68K (which lacked floating point) provided the software function m(x, y, 16) which solved a number of practical problems.
There were some ideas proposed for ADA in support of such practice.
Here is what I would propose for a higher level language.
Provide a parameterized scaled type effectively declaring that the variable would be some multiple of r, a rational, and within a definite range of values.
The range part is like Pascal and perhaps one of the ADA proposals.
If the programmer always chose r as a power of two then adds could be done with fixed shifts, as it was done in 1955.
The sum and the product of two numbers of different such types, is of such a type.
If the declared ranges were less than the memory words, then not all additions would require a compiled overflow test.
Applications that need fixed decimal behavior are naturally and efficiently supported
in binary formats.
Special notation that I will not try to invent here would sometimes be necessary to scale the subexpressions.
Hardware Floating Point
Livermore had fairly complex numerical calculations and few there dealt with issues of precision.
Empirically production programs ran better after translation to a platform with more precision.
Until the 360 there were no common machines which provided more than one floating precision.
The earliest binary computers adopted a 36 bit fixed point format that would be scaled by program logic—each variable and subexpression was scaled by some power of 2 by explicit program logic.
When the 704 introduced floating point it was argued that the loss of 9 bits of precision that the exponent displaced would be made up for by providing more precision in those cases where compile time scaling was too pessimistic.
The extreme convenience of floating point quashed any contrary arguments.
Fixed point physics was dead!
Several other systems adopted the 36 bit floating format, with variations.
Burroughs and CDC computers adopted 48 bits which held a floating value more comfortably.
Later the Stretch and the 6600 moved to 48 bits of floating precision within 64 and 60 bit words respectively.
Numerical problems in the big production codes diminished with this new precision and there was little incentive to seek problem formulations requiring less precision.
The 360 defined what real number was represented by any particular combination of bits within a floating point value and baldly declared that add, subtract, multiply and divide would in each case produce that floating point value closest to the mathematically defined value.
Out of range exceptions were, of course, noted.
The model 91(??) used a divide algorithm that might be one bit off in the last place.
This was documented.
It was a very fast divide.