9

Most pre-IEEE floating point formats that used biased exponent and a 2's-complement mantissa without a hidden bit, were asymmetric. That is, there was a representable negative value which had no positive equivalent, analogous to 0x80000000 in 32-bit integers.

Printing floating-point numbers typically involved checking if it is negative, negating it if it was, then proceeding with the conversion of the guaranteed non-negative number, taking care to prepend the minus sign if necessary when outputting the text representation.

Naturally, that technique would fail if the argument of the printing routine was the maximal-magnitude negative value. How did the pre-IEEE systems which employed such floating point formats handle the situation? Did any bother to include the code for that special case, or letting the program incur a floating point exception was the norm?

The question is focused on systems with hardware FPUs.

See, for example, a description of the PDP-10 floating point format, mentioning the two's complement format. It says,

Single precision floating point numbers are represented in one 36 bit word as follows:

 0 00000000 011111111112222222222333333
 0 12345678 901234567890123456789012345
 ______________________________________
| |       |                            |
|S| EXP   |     Fraction               |
|_|_______|____________________________|

If S is zero, the sign is positive. If S is one the sign is negative and the word is in twos complement format.

The fraction is interpreted as having a binary point between bits 8 and 9. The exponent is an exponent of 2 represented in excess 200 (octal) notation.

In a normalized floating point number bit 9 is different from bit 0, except in a negative number bits 0 and 9 may both be one if bits 10:35 are all zero. A floating point zero is represented by a word with 36 bits of zero.

Floating point numbers can represent numbers with magnitude within the range 0.5*2^-128 to (1-2^-27)*2^127, and zero. A number that in which bit 0 is one and bits 9-35 are zero can produce an incorrect result in any floating point operation.

from which it is not immediately clear if that restriction makes the useful set of numbers symmetric. Even if it did, how did the printing routines react to the aforementioned bit pattern?

Upd. For the PDP-10 predecessor PDP-6, the floating point format was described in a somewhat more permissive way, see page 27 of the PDF.

For an example of the issue on the CDC 6600 platform, see CERN Computer Newsletter, N 15, page 2, 6600 SYSTEM BUGS:

The input/output conversion routines go into a loop ... the phenomenon appears to be that a number with unnormalised mantissa, and exponent of −0 fails whereas if the mantissa is normalised with exponent −0, conversion is correct.

user3840170
  • 23,072
  • 4
  • 91
  • 150
Leo B.
  • 19,082
  • 5
  • 49
  • 141
  • 1
    There weren't many FPUs prior to IEEE (aka 8087) and not all base two - like the /360 used base 16. – Raffzahn Jul 14 '22 at 17:24
  • 2
    @Raffzahn The base is irrelevant for this. What matter is that the IBM format was sign-magnitude rather than 2's complement, so it was not susceptible to that issue. – Leo B. Jul 14 '22 at 18:28
  • 1
    Of possible interest is Guy Steele and Jon White's “How to Print Floating-Point Numbers Accurately – texdr.aft Jul 14 '22 at 18:31
  • @jonk That is true; ideally, I'd like to see examples of each conceivable behavior (crashing, special-casing, incorrect output); primarily, I'm interested in how the special-casing was done, if ever (checking for the "magic" value and manipulating a pre-generated string, or employing a different algorithm which avoids negation of the initial untrusted value, etc.) – Leo B. Jul 14 '22 at 18:33
  • 1
    @texdr.aft Thank you; I remember hearing about the article, but I have not seen it before; however, the words "negative" and "magnitude" appear only two times each and in irrelevant contexts, which means that the authors were not concerned anymore about asymmetric formats. That is not surprising given the article date (1990, post 1987). – Leo B. Jul 14 '22 at 18:39
  • @jonk Please see http://pdp10.nocrew.org/docs/instruction-set/Floating-Point.html – Leo B. Jul 14 '22 at 22:23
  • @jonk I am not going to play that kind of mind games. I've led you to the water, it is your decision whether to drink. – Leo B. Jul 14 '22 at 22:38
  • If a floating-point output function can robustly handle situations where rounding may cause the numerical value that is output to exceed the range of the type (as could occur if trying to perform output equivalent to e.g. printf("%10.4g", 1.79769E+308);, which would output 1.798E+308, which is larger than the maximum IEEE-754 double) it would need to handle numbers with a very large exponents specially regardless of whether they're positive or negative, e.g. by reducing the base-two exponent by 485 and recording the fact that one needs to subtract 146 from the base 10 exponent and... – supercat Jul 15 '22 at 00:05
  • ...subtract a compensation factor of about 1.04154759E-3 times the result). – supercat Jul 15 '22 at 00:07
  • @supercat A robust printing/scanning roundtrip is yet another game. – Leo B. Jul 15 '22 at 00:44
  • @LeoB.: One which could be helped by specifying that a numerical value of the output must be within 0.25ulp, and scanning function must yield a result within 0.7ulp of the numerical value of its input. Some systems try to output the minimum precision necessary to yield a correct round-trip by a perfectly-rounding import function, but that's more complicated than iterating until the residue is within 0.25ulp, and requires that code which reads the data be more complicated than would otherwise be necessary. Using Kahan summation, it's easy to write output routines that can quickly... – supercat Jul 15 '22 at 15:28
  • ...output a value while estimating the residue to within a less than 1/100 of an ULP of the original value. For example, one can estimate a power-of-ten exponent by dividing the power-of-two exponent by 10 and multiplying by 3. For each power of ten exponent 3N, one can then compute the nearest floating-point value to pow(1024,N)/pow(1000,N), and the nearest floating-point value to the difference between the mathematically-correct value and the other floating-point value. – supercat Jul 15 '22 at 15:39
  • Another potential source: https://dl.acm.org/doi/10.1145/362851.362887 (“In-and-out conversions” by David W. Matula, 1968) – texdr.aft Jul 20 '22 at 02:43
  • 1
    @texdr.aft Thank you, but that is tangential to my question. I want to know if there were platforms which (a) had an asymmetric - for example, 2's complement of some form - f. p. representation, and (b) could handle the "extreme" values in a robust manner. The sole occurrence of "negat" in the article appears in the context implying a symmetric representation. – Leo B. Jul 20 '22 at 03:57
  • Given all the issues with floating point conversion to/from base 10 decimal values (ASCII), and the many academic papers that have been written, over the years, on how to do it right, it is kind of surprising to me that, in this era of dark silicon and vastly increased instruction sets and enormous microcode stores on chip, that Intel/AMD haven't provided machine instructions to do it correctly once and for all, eliminating the problem. They could even support variations, e.g., exact representation vs shortest ascii representation that round-trips. (Or maybe they do I I just don't know?) – davidbak Jul 22 '22 at 17:15
  • Here's a PDP-10 floating-point (“flonum”) output routine, written by Guy Steele, for Maclisp: https://github.com/PDP-10/its/blob/master/src/l/print.306#L1582. This update claims that “(1) It outputs enough digits that no information is lost (a perfect reader, which I do not yet claim Lisp has, can always recover the original flonum by rounding). (2) It outputs no more digits than necessary to achieve (1). (3) It outputs the best possible decimal flonum of the number of digits in (2).” – texdr.aft Jul 25 '22 at 16:25
  • @texdr.aft That routine starts with negating its argument if it is negative. That means that it would likely incur an FPE or produce garbage when given a number that in which bit 0 is one and bits 9-35 are zero, unless %NEG% is a macro which handles that case gracefully. – Leo B. Jul 25 '22 at 17:03
  • That CDC 6600 didn't use two's complement, it could easily have a -0.0. – Mark Ransom Jul 27 '22 at 18:44
  • @MarkRansom I know. That was an example of a pre-IEEE system with a non-robust floating point output routine. – Leo B. Jul 27 '22 at 18:49
  • I didn't follow the link you provided for the Cyber. The part you quoted made it seem like there was just a step that needed to be added to make it robust. – Mark Ransom Jul 27 '22 at 19:32

1 Answers1

3

The primary advantage of two's-complement numeric representations over other forms is that the bottom bits from an addition or subtraction can be computed in a manner which is agnostic to the signs of the operands. Such an advantage does not apply with floating-point numbers, which require treating operations whose result will be larger than the larger operand differently from those whose result will be smaller. As a consequence, two's-complement floating-point representations have always been rare.

As for more general questions about robustness, many floating-point output routines were designed to output values with somewhat less precision than was available in the underlying types, and produce a value that was hopefully within +/-1 of the least significant digit that was output, but with "hope for the best" rounding semantics beyond that. If one has a floating-point number in the range 0 to 1, a simple way of outputting it would be to do something like:

void out_number(double x)
{
  char dig;
  putchar('.');
  for (int i=0; i<num_digits; i++)
  {
    x*=10.0;
    dig = x;
    x -= dig;
    putchar('0'+dig);
  }
}

Such an approach can easily output any desired number of digits, but will be limited in the number of correct digits it can output, especially when used with values near zero. If the number of digits the function tries to output is limited to what it can handle accurately, there may be no need to do anything fancier, and as a consequence many floating-point implementations didn't.

supercat
  • 35,993
  • 3
  • 63
  • 159
  • 1
    That answer does not answer the question as asked. Sure, there are precise algorithms to output fractional parts of floating-point values, and I'm aware of at least one implementation that used such an algorithm, so that a number represented as 0.1111...111 * 2^0 (with 40 1's in the mantissa) would be printed using the Fortran format F50.45 as 0.999999999999090505298227071762084960937500000 which is (2^40-1)/2^40 exactly. – Leo B. Jul 14 '22 at 20:09
  • 1
    The document https://hal.archives-ouvertes.fr/hal-00157268/document (2003) states Few designs, mostly those of Texas Instruments, continue to use two’s complement floating point units. Such units are simpler to build and to validate... The TI format has a hidden bit, but the same must have been true for formats without the hidden bit. – Leo B. Jul 14 '22 at 20:17
  • @LeoB.: I'll have to take a look at that, but I find dubious the proposition that two's-complement designs are simpler to validate. Maybe the asymmetry is less bad than the way IEEE-754 handles "negative zero", but sign-magnitude math doesn't necessitate such asymmetry. IMHO, zeroes should be made symmetric by having four kinds of zero-ish things: true zero (universal additive identity), positive infinitesimal, negative infinitesimal, and indeterminate-sign infinitesimal [produced by adding any two different infinitesimals]. – supercat Jul 14 '22 at 20:37
  • I've added a concrete example: the PDP-10 had a two's-complement FP format, with some bit patterns declared illegal (or implementation-dependent?). – Leo B. Jul 14 '22 at 23:01
  • Just adding to @LeoB. 's comment: In 1992 was using a TI C31 DSP as a graphics "Transform and Lighting" accelerator on a prototype 3D PC add-in-board. Had to have functions to map from the PCs IEEE to TI's 2s-complement format. (The need for the DSP disappeared when the Pentium arrived, and the prototype board evolved into PowerVR) – Simon F Jul 27 '22 at 07:39