11

The floating-point format on the ZX Spectrum has the unusual feature of special-casing small integers: Why does Sinclair BASIC have two formats for storing numbers in the same structure?

There are excellent and sufficient reasons for this. Basic programming tends to be casual about types; many variables that could hold a floating-point number, in fact only hold small integers. The machine had no FP hardware; floating-point calculations were much slower than integer calculations. The upshot was that this design decision made many programs run a lot faster.

No one repeats it nowadays because there's no point. All modern general-purpose computers have floating-point hardware.

It seems to me there was an intermediate time, in the sixties for mainframes, seventies for minicomputers, eighties for microcomputers, when it could've made sense because floating-point hardware was possible but unusual. For example the IBM PC could have an 8087 installed, but most of them didn't.

The common strategy in this situation was to just go ahead and use IEEE format as though a coprocessor were expected: https://en.wikipedia.org/wiki/Standard_Apple_Numerics_Environment

Some programs used a different format designed for software floating-point: Did any PC software floating point use non-IEEE format?

But as far as I can tell, nothing else used a format that treated integers as a special case and made an effort to run them faster.

In many contexts, this makes sense; there would be no point treating integers as a special case if you expect numbercrunching workloads to consist of things like fluid dynamics simulations, as the likes of Cray did. The two kinds of workloads that do tend to have lots of integers where floating-point numbers were expected, are Basic programming and spreadsheets (well, scripting languages in general; JavaScript is a modern example, but was invented after all CPUs started including IEEE hardware), and IEEE 754 was not developed with those primarily in mind.

Was there ever any floating-point format designed (unlike the Spectrum) on the basis that hardware support was possible but uncommon, that was designed to fast-track operations when the operands happened to be small integers?

5
  • 11
    I think there's a misconception in the question: The ZX Spectrum simply stored integer variables and FP variables (not: "two floating-point formats". And all computers still do that today - working with integer variables is still faster that FP in many cases, even when you have an FPU.
    – tofro
    Jun 21 at 6:42
  • 1
    Didn't the industry-standard MS BASIC from the 1980s allow the programmer to declare integer variables and use optimized integer arithmetic when the operands were declared as integers?
    – Brian H
    Jun 21 at 17:30
  • 1
    @BrianH To my knowledge MS Basic allowed you to store integer variables. But before calculating any expression involving them, it first converted them to floats, did the calculation in the FP domain, then reverted them back to integers. That was not particularly fast.
    – tofro
    Jun 21 at 19:56
  • 1
    FP math is complicated, and needs a lot of transistors to make it fast. Integer arithmetic is really easy. You speed it up by speeding up the ALU itself. Thus, no need to have external HW for anything except maybe integer division.
    – RonJohn
    Jun 21 at 20:09
  • 1
    I suspect the transistor budget to implement this dual format was better spent on just making the floating point faster. Inside every IEEE binary64 is a 53-bit integer hiding in plain sight. Jun 22 at 0:05

4 Answers 4

16

Computer architectures designed by Sergey Lebedev did not have a separate integer unit. Integers were represented as unnormalized floating point values with the exponent chosen to make the LSB have the value of 1.

E. g. on the BESM-6, the normalized representation of the value 1.0 is (in octal) 4050 0000 0000 0000 (7 bit exponent, sign, 41 bit mantissa, no hidden bit) that is 0.12 * 265-64. The integer 1 was 6400 0000 0000 0001, with the 1 bit in the least-significant position and the exponent incremented accordingly, which allowed to use the value in floating point operations unmodified, except as a divisor, which had to be normalized.

Fast-tracking of additive and multiplicative integer operations consisted of suppressing post-normalization (and given that the exponents of integer values are always the same, pre-normalization for additive operations was not required), and, for multiplication, of copying the low bits of the product from the special register to the result register (the accumulator).

Also see this old question of mine. While the theoretical maximum latency of a floating point operation was quite high, the average, thanks to "fast-tracking", was more than an order of magnitude lower.

11
  • 1
    How was array indexing handled? If using row-major subscripts, would one have to expend special efforts to ensure that given ARR{10,4) an attempt to access ARR(2.5,3) wouldn't access ARR(3,1)?
    – supercat
    Jun 21 at 16:40
  • @supercat I don't see how it relates to the topic. Please clarify your question. Moreover, in Fortran (to which you presumably allude), there were no bound checks.
    – Leo B.
    Jun 21 at 21:46
  • 2
    @supercat As Fortran is column-major, I had to change the indexing to exhibit the issue. The oldest Fortran compiler is silly: ARR(3, 2.5) returns the value assigned to ARR(8, 2); another one takes ARR(3, 2.5) as ARR(3, 2); the third one (written in the GDR) errors out with SUBSCRIPT OF ARRAY HAS NOT INTEGER TYPE. This has nothing to do with representation of floating point or integers, it is purely a compiler issue.
    – Leo B.
    Jun 21 at 22:11
  • 1
    Based on your last comment, I hope you figured out how my comment relates to the issue: multi-dimensional array indexing inherently involves integer multiplication and would be a bit awkward if all that existed were floating-point values, though I guess if one had a machine where all addresses were within the same power-of-two range it may be possible to treat the bottom portion of the mantissa as an address without having to do anything special to force the exponent value.
    – supercat
    Jun 21 at 22:36
  • 1
    @supercat To use a number as an address, it has to be denormalized. The 15 least-significant bits of mantissa are used as the address.
    – Leo B.
    Jun 22 at 16:53
3

In addition to the modern software implementation I mentioned, the LISP machines (from LMI and Symbolics) were notable for their hardware support for dynamic typing, including the ability to store either fixed-point or floating-point numbers and distinguish between them at runtime, with support in the ISA (implemented with microcode). Later machines in the series even had instructions that would trap if a datum were not the correct type.

The Cray-1 supercomputer from the same time period was also notable for being able to store either 64-bit integers or floating-point numbers, or vectors of up to eight of either, in the same registers and perform either kind of operation on them. The hardware itself did not define a single format comprising both, but it would be easy to test for a particular pattern of bits using its logical instructions. It also had an instruction to convert between an address register (such as a loop index or array offset) and an un-normalized floating-point number.

2

The question may be flawed due three misleading shortcuts:

  1. Sinclair doesn't do hardware float, it's storage issue resulting in selection of according math routines for speed optimization on a restricted 8 bit CPU. Systems utilizing hardware FPU are usually way less constrained.

  2. The difference between hardware based float and hardware based integer isn't as noticeable (if at all) once a hardware FPU is available - not to mention that integer is, within boundaries, a strict subset of float.

  3. Next to all CPU have dedicated instruction sets for integer and float. Architectures that allows the handling of integer within their floating point units, like the 8087 and it's ofsprings, do so by converting them to and from float (*1) and using float instructions thereafter.

Oh, and a fourth one (*2):

  1. Float vs. Integer is a use case szenario. The assumed 'there was an intermediate time, in the sixties for mainframes' never existed. For one, the huge majority of mainframe applications were integer (*3) not needing float at all (*4), or they were float based, needing Integer only for program flow which is best handled in the (integer part of the) CPU anyway.

Which directly leads to the special case of BASIC:

  1. The Spectrum is a very specific case growing out of a need for optimization in a language that does handle integer only as second class citizen. Originally every numerical value in BASIC is float (*5). But while float may be only a bit slower on a machine with an FPU (*6), it's a disaster whens handled by an 8 bit integer CPU. Adding a few tests for integer values and using integer function whenever possible costs little compared to the possible performance gain. In fact, it saves, in addition, the need for an integer type.

Classic Win-Win.


*1 - Well, it's a bit more complex like that conversion is done into and from an internal 80 bit float, which guarantees 18 decimal digits without rounding, thus effectively acting as if it's an 18 digit decimal or 64 bit integer - at least as long as only likewise numbers are handled. Every operation with a float outside 64 bit Integer range will result in FP based rounding artefacts.

*1 - that's a classic off-by-one, isn't it?

*3 - Or more exactly integer and BCD

*4 - Well, I know some using float registers as scratchpad :))

*5 - Dartmouth BASIC and many other (early) BASICs of the 1970s did not have Integers at all. Even a function like INT() returned a float. String was the first different type to be added, Integer came only very late and not much supported.

*6 - Something the low cost Sinclairs for sure never had coming :))

8
  • 1
    @PeterCordes that's exactly the point of the 'within boundaries' constriction. The points made are generic. Note that no specific formats are mentioned, just the basic relation. Of course is a 32 bit float not able to hold a 32 bit int, but it can do so for any Integer 24 bit or less. And yes, it's exactly why Intel choose a 80 bit temporary float as it's 64 bit mantissa will be able to hold any 64 bit integer (and thus 18 digit BCD) without loss of precision.
    – Raffzahn
    Jun 21 at 14:00
  • 1
    I'm not sure why it's relevant, though. Choosing an actual integer type gives you a larger usable value-range for the same width. The obvious (to me) comparison is 32-bit int vs. 32-bit float, not 24-bit vs. 32-bit. That's why I had to guess what your "within bounds" was getting at, because that isn't the bound of the value-range of the float or a same-sized int. Jun 21 at 14:11
  • 1
    @Raffzahn: The other reason for using a 64-bit mantissa is that on a 16-bit or larger processor without an FPU, computations with a 64-bit mantissa that does not have an implied '1' will be just as fast as computations with any smaller size. IMHO, it's unfortunate that IEEE-754 didn't offer any recommendations for a 48-bit 'long float' type applying the same principle, since on many machines computations with such a type could be performed more efficiently than computations using float (and way faster than double) while offering more precision than the former type.
    – supercat
    Jun 21 at 16:36
  • 2
    I don’t see how the question is flawed in light of this. It states, “No one repeats it nowadays because there's no point. All modern general-purpose computers have floating-point hardware.” The question is asking whether any other systems have dynamic number formats
    – Davislor
    Jun 21 at 17:24
  • 2
    Most mainframes, being Big Blue and running COBOL, did Integer and BCD arithmetic.
    – RonJohn
    Jun 21 at 20:03
1

SQLite to this day uses a dynamic type system in which integers, IEEE floating-point numbers, and other things are possible datatypes, selected by a storage class field in the format.

It’s common for languages with dynamic typing to hold data in this kind of discriminated union, but SQLite might be the only general-purpose database in wide use to write its data to disk in this format. Other databases associate a static type with each column.

I am not aware of any architecture with hardware acceleration for this, though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.