TIL: numbers don't always print the same way

(merely trying out a today-i-learnt tag)

Numbers may not print the same depending on the underlying formatter, even for base Racket. For example:

> (~r 1234567890123456789.123)
"1234567890123456768"
> 1234567890123456789.123
1234567890123456800.0

@ryanc explains why:

It seems to be a consequence of the following:

> (inexact->exact 1234567890123456789.123)
1234567890123456768

Here's what I think is happening:

  • One view of a floating-point number is that it represents a set (an interval) of reals. The printer's goal is to print some finite representative so that the reader can identify the correct set and produce exactly the same floating-point number. A secondary goal is to prefer zeros in "don't care" positions to avoid misleading humans about how much precision is actually present.
  • Another view of a floating-point number is that it represents a specific rational number (calculated from the mantissa, exponent, etc). This example represents an integer, and inexact->exact is producing that specific integer.

Arguably, ~r should be doing the first thing, but it's doing the second thing instead. Mea culpa. On the other hand, this behavior is consistent with the docs sentence "The exactness or inexactness of x does not affect its formatting", and the first behavior is not.

4 Likes

The value printed by ~r is correct, but I am surprised by the value printed by the REPL and I don't know where that one is coming from.

The number 1234567890123456789.123 cannot be represented exactly as a flonum (double precision floating point) value. The number is so big that it can be represented only as an integer and, at this magnitude, flonums can only represent every other 256th integer. So, this number has some interesting "properties":

> (require math/flonum)
> (define x 1234567890123456789.123)
> (integer? x)     ; it is an integer
#t
> (= x (+ x 1))    ; it is equal to the number next to it
#t
> (- (flnext x) x) ; next representable number is 256 positions away
256.0

Internally, 1234567890123456789.123 is stored as 1234567890123456768, this is the value that ~r receives and it displays it corectly. This is not a bug in Racket, the behavior is consistent with other programming languages. Here is a C program that produces the same result:

// Save as magic.c, compile with "gcc -o magic magic.c -lm"

#include <stdio.h>
#include <math.h>

void main()
{
    double x = 1234567890123456789.123;
    if (trunc (x) == x) {
        printf ("this is an integer (truncating produced the same number)\n");
    }
    if (x == (x + 1)) {
        printf ("adding one produces the same number\n");
    }
    printf ("x = %.20g\n", x);
}

I am surprised that the Racket REPL displays this value as 1234567890123456800.0 -- this value is also stored internally as 1234567890123456768, so it is the "same one".

Alex.

4 Likes

You might be interested in the paper "Printing Floating-Point Numbers Quickly and Accurately" by Burger and Dybvig (https://legacy.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf). The introduction nicely explains the goals of the printer. Compared to that paper, Racket's printer seems to replace # with 0, maybe for compatibility with other systems and less confusion to human readers. The ~r function does not use the algorithm in that paper; it was on my todo list (the comment is still there), but I never got around to it.

5 Likes

Thanks for the link to the paper -- the value printed by the REPL does make more sense now, and now that I read the paper, I would prefer it to be printed as 12345678901234568##.# -- even if this means opening the Racket Guide or asking on forums such as this one about its meaning.

Interestingly, the documentation does mention the #, but without context, so it appears as an oddity to someone who is not familiar with the paper or Chez Scheme.

In any case, I learned something new and interesting today, and I thank you for that.

Alex.

3 Likes

I did some research of the meaning of # in the number syntax here:

syntax - Meaning of # in Scheme number literals - Stack Overflow

3 Likes