The smallest number

2007-07-05

SBCL doesn’t correctly read this number:

* 2.4703282292062328d-324

0.0d0

Bad Lisp. To your room and no snacks.

Python does:

>>> 2.4703282292062328e-324
4.9406564584124654e-324

The reason Python reads «2.47…» but then prints «4.94…» is because the value that it converts the input to is the smallest positive number in IEEE 754 double precision. As I pointed out whilst I was badmouthing the C# standard this is 2-1074. The number immediately below 2-1074 in the double type is +0 and the number immediately above it is 2-1073. Thus for x such that 2-1075 < x < 3*2-1075, x gets converted to 2-1074 because that’s the nearest member of the double type.

The shortest way to write this number (which I suggest is best) is 5e-324. But because the relative input error is so large for this denormal, we can use anything from 3e-324 to 7e-324 (we don’t even have a single digit of precision when our denormals get this small).

Reading the Common Lisp Hyperspec I’m a bit shocked. There doesn’t seem to be any discussion of the meaning of a number when it is read. In other words whilst the spec says that reading «1.1» results in a number, it doesn’t say what number it is (does it? It wouldn’t be the first time that I’ve missed something obvious). It doesn’t say this even for integers. For integers we can take the stunningly obvious as read: an integer when read is converted internally to an integer that has the same mathematical value as its external representation. For reals it’s not so clear. In most (all?) implementations the real number 1.1 will not be representable exactly, we’ll have to choose some number to approximate it. A sensible thing to do is to choose the member of the target type that is closest to the mathematical value of the decimal number. This allows (as long as printing is good enough) floating point numbers to be printed and read without loss.

It seems like an argument could be made that when the reader underflows a number to 0 (as SBCL did with my first example) then it should signal a floating point underflow error. “Should” in the sense of “at safety level 3”. SBCL doesn’t signal an error for this code: «(progn(declare(optimize(safety 3)))(expt 2d0 -1075))», so I don’t hold out much hope of seeing the reader signal an error.

Of course, SBCL is perfectly capable of representing and printing the smallest number:

* (expt 2d0 -1074)

4.9406564584124654d-324

I guess I’ll have to read that Clinger paper after all.

Stop Press: CLHS doesn’t, strictly speaking, support floating point formats that use denormals. Bad standard.

2 Responses to “The smallest number”

  1. glorkspangle Says:

    Pants to the spec, eh?
    We have to learn to call them subnormals, apparently. So saith 754r.

  2. drj11 Says:

    Or, er, maybe Common Lisp does support denormals. There’s a glossary entry for denormalized numbers, and the page on float-digits and float-precision clearly explains the difference between the two functions when the argument is a denormalized number.

    Ah. A more careful reading of the float type reveals that Common Lisp does (optionally) support denormals. The key point being that in characterising a number as s*f*b**(e-p), p is allowed to vary depending on the number, and is not a parameter the floating point type. So p is 53 for normalized doubles, but is < 53 for denormalized numbers.

    Oopsie me. Of course Lisp supports denormalized numbers, what was I thinking?


Leave a comment