Anyway, all this love for steampunk instruments makes one wonder why they concentrated on the almost entirely fictitious life of cowboys and guns in films and books of the Old West and not on freelance mercenary engineers and wandering hired mechanics.
‘Have Sliderule, Will Travel’.
I’ve always wondered if it wouldn’t be worthwhile to include as a feature of CPUs that they would automatically take the logs of all numerical variables that appear in a program and have them shadow the variables, whenever processor cycles started being thrown away. They’d be sitting there for you whenever you decided you wanted to multiply, or take them to a power, and so forth.
No. Binary multiplication is very efficient, and binary division is not far behind it. And most of what CPUs do is integer math, in great freaking gobs, wasting time logging values would be, well, a waste of time. To take a log, you would have to convert a binary integer into some non-integer representation, which can cost you accuracy, depending on scale. And even if you are talking about binary floating-point numbers (which are already stored in a sign/exponent/mantissa format), multiplication and division are still very efficient, taking logs would just cost way too much.
ForTran is a high-level language, as such it obscures the cost of taking a log. Granted, some older systems did have tables for common numbers that made getting a log as fast as an addition for some values, but it is still an extra step.
And natural logs are, well, natural: the result of a calculated regression. Base-10 logs make sense to us because they make sense to us, but without a table, a computer has to get the log[sub]e[/sub], then divide it by a constant (log[sub]e[/sub]10 – or if you want to avoid division, e [sup]( ( log[sub]e[/sub] ( log[sub]e[/sub] x ) ) - ( log[sub]e[/sub] ( log[sub]e[/sub] 10 ) ) )[/sup]). Natural logs are slightly faster (less computation).
If she had used Hungarian notation for her variables (the original Apps Hungarian version, not the bastardized System version that became popular), this would never have been a problem. See here for an essay on the subject; scroll down to near the end for a specific discussion of Apps vs System Hungarian.
The point was not about speed (although that may have indeed helped considerably) but whether the machine could usefully represent the needed range of values without underflow or overflow. Machines in days of old tended to be optimised for “scientific” or “business” needs, and a business computer may actually have not provided a useful representation at the machine level. Just a thought.
Certainly on a modern machine multiplication of floating point is fast, division tends to be slower. But modern machines can dedicate entire slabs of silicon to the task. Older machines had no such luxury, and a floating point division (especially) could take ages in comparison to a subtraction. Worse, there were not multiple arithmetic units running in parallel, you simple had to churn the operation around in the one poor unit. The joys of a microcoded architecture insulated people from a huge amount of grief about what really went on, and just how dreadfully inefficient some operations could be. This really didn’t go away until the 90’s for many users.
It wasn’t that long ago that a floating point division could be 10 to 30 times slower than a floating point subtraction.
I’ve seen such large cylindrical slide rules at least three times. For a long time they had one on exhibit under the Capitol Rotunda in Washington DC (evidently one such rule was used in designing the dome). Another resided in the Undergraduate Math Office at MIT. (I haven’t been to the Capitol in years, so I don’t know if it’s still there after all the redesigns and reshuffles. And the MIT undergrad office has been in turmoil for the past couple of years as they remodel).
Can’t recall where I saw the third.
The ones I saw didn’t look like the pictures in the Wiki article – the drum was substantially larger, and there was a fixed bar that ran across the rapped rulings.
The code I was working with was not structured such as to avoid overflows and underflows. In fact, even with using logs, and even on modern computers, it still had overflows and underflows, a few of which I was able to fix.
I’m also aware that a good notation convention (such as Hungarian) in the variable names would have helped tremendously, but I didn’t have the freedom nor the time to convert it over. What I actually ended up doing was to maintain my copy of the code in rich text (together with a script to convert it back for compiling), and using colors, subscripts, and superscripts to annotate the variables.
And my comment about multiplication and division being cheap nowadays was in response to Napier’s suggestion of shadow variables which already have the log taken. It’s possible that that might once have been a good idea (in fact, it’s possible that some computers of the day did it), but it wouldn’t make sense to do it now.
Over a range of known values, the computer can use a look-up table to get logs as quickly as one addition (indexing). But when values fall outside the range of the table, they have to be calculated. The algorithm for calculating an arbitrary base ten log, written in contemporary C, would look something like this
(Or it could be written to iterate a fix series of terms, since this would fail for really small fractions, or it could combine iteration with “seriesTerm > 0”.)
Obviously, with all those multiplications and divisions, taking an arbitrary log on a computer can be expected to be computationally costly, probably more costly than just doing the straight multiplication or division, especially when you add in the e[sup]x[/sup] conversion back to the original number.
I could see, possibly, building some sort of waveform generator that could be used for analog log / exponent computation (as well as other things, like trig functions) using DAC to put the number in and ADC to take the result out – that would be very fast but, just like the slide rule, it would sacrifice some accuracy.
(The above code is incorrect, and I missed the edit window: it fails to account for the alternating sign of the series terms. But that is computationally trivial compared to all the multiplications and divisions the series involves.)
That is because modern processors do something kind of like
The method of multiplcation is one that I don’t understand, but it certainly involves table lookups and addition.
The shadow variables are achieved by speculative computation: the processor sees that a multiplication is coming up, and starts doing it early, well before the result is required.
Modern processors are high-level language processors (and have been, more or less, since I was studying in the early 80’s). The commands you send to them are broken down into programs that are executed by something you can’t see at all.
FORTRAN and Fortran were languages designed for doing calculations. Part of the reason you had to wait for them to do things like “log” is because the rest of the language was so efficient that it took hardly any time to do anything.
I’m working with embedded C now, and a major souce of change in the language definition over the last 15-20 years has been to make it as fast as Fortran. Which followed, naturally, after the standardisation of the standard c math library, which had been the main barrier to the conversion of FORTRAN programs to C.
Fortran was and still is a language directed at scientific computing. Modern Fortran has adopted a large range of “modern” language features, right up to support for object oriented programming.
The Fortran of yore, which most people associate with Fortran IV, was a rather different beast in terms of programming. It was very limited in capability (as a language, not in terms of access to support libraries, which in supporting mathematical functions became quite significant.) If did however define in the language a (for the time) reasonably sophisticated formatted IO capability.
Everything about Fortran was about computing mathematical expressions, and in particular expressions involving arrays. Matrix arithmetic was a key capability, and solver libraries a key part of a programmer’s arsenal. Fortran supports complex numbers as an intrinsic type. This is a huge win, and one that so many in the OO community never got. When the compiler can optimise complex arithmetic operations it can identify a vast range of optimisations that are invisible if the complex number has been implemented as a class library. (The Java guys (Gosling et al) never understood this.) But in terms of programmability, Fortran IV was dire. No recursion unless you implemented it yourself. Common variable blocks - often used to pass parameters (just say no). Implicit variable types. I refuse to touch Fortran code if it doesn’t have “implicit none” right at the top. If it doesn’t, the first job is to work through the code until there are no implicit variables.
Most Fortran code you see from days of old are a mix of terrible unstructured computed goto statements that are used to manage the overall program flow, and routines that are built around nested loops doing iterative work on arrays of numbers. A lot of effort was, and is, put into optimising these loops. Even early compilers aggressively performed common self-expression elimination and code hoisting. Modern compilers are good at parallelising such code with little more than a few hints in pragmas. Cray did a huge amount of work on compilers in the early days directed at their vector machines, and the Cray extensions were influential.
That is absurd. For fun, I wrote multiplication and division for a 6502 many years ago. Think about how you multiply on paper: you memorize the products of 1~9 from a table and do each pair of digits, summing and carrying. Now, how big is the table for a binary computer? To multiply, you check each bit of one number and add the other number to the sum if it is one, shifted by the position of the bit, just like on paper. There is no table. On modern processors, this can be almost as fast as addition, because they build the logic to look at several bits at once and use a “barrel-shifter” to pump out several numbers to be summed.
Division is equally simple and intuitive. On paper, you eyeball the numbers and estimate the next digit, but in binary, the next digit can only be one or zero, so either one goes in and there is a subtraction, or zero does and the divisor is just shifted for the next comparison. Division is much harder to streamline for arbitrary values, but I suspect there are ways.
Floating-point math is slightly different, but the basic principle is the same. The numbers are stored as a high-order-justified binary mantissa and a binary exponent, so they are pre-aligned for scanning, and the exponent is added or subtracted just as with a log’s characteristic to get the order of the result.
I don’t pretend to know what that means, but in discussion of multiplication errors in the x86 processor, I thought they used a table to do something like a loagarithmic lookup as part of the process?
The Pentium flaw was division, not multiplication. I guess they were using a table and a complicated algorithm to double the speed of division by outputting 2 bits at a time. But the principle is still the same, scanning the bits and subtracting. With multiplication, you can basically do it all at once if you want to commit that much hardware. Fundamentally, though, it is still the same process. I do not believe that tables would make multiplication any faster, just more wires and gates.
I think the Pentium stored permanent values in a lookup table that they used for the first few digits of division, and they used some iterative method beyond those digits. The flaw was that they had some missing values loaded into the table.