[Python-Dev] RE: [Python-checkins] CVS: python/dist/src/Doc/tut tut.tex,1.133.2.1,1.133.2.2

Tim Peters tim.one@home.com
Mon, 21 May 2001 14:29:02 -0400


[Fred checkin]
> > ***************
> > *** 2610,2617 ****
> >   \begin{verbatim}
> >   >>> x = 10 * 3.14
> > ! >>> y = 200*200
> >   >>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
> >   >>> print s
> > ! The value of x is 31.4, and y is 40000...
> >   >>> # Reverse quotes work on other types besides numbers:
> >   ... p = [x, y]
> > --- 2610,2617 ----
> >   \begin{verbatim}
> >   >>> x = 10 * 3.14
> > ! >>> y = 200 * 200
> >   >>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
> >   >>> print s
> > ! The value of x is 31.400000000000002, and y is 40000...
> >   >>> # Reverse quotes work on other types besides numbers:
> >   ... p = [x, y]

[Guido]
> Hmm...  The tutorial now contains at least one example of floating
> point imprecision.  Does it also contain text to explain this?  (I'm
> sure Tim would be happy to provide some if there isn't any. :-)

[Fred]
> It contains others, and I don't think there's an explanation.  Some
> text from Tim to explain this would be greatly apprectiated!

Actually, 31.400000000000002 wasn't a true improvement over the earlier 31.4:
so long as we rely on the platform C to format floats, the output isn't
well-defined (the last digit or so can and will vary across boxes).

I can certainly explain that this is so, and even why, but unsure the
tutorial is the right place for it.  In any case the tutorial shouldn't be
giving examples whose output is platform-dependent.  For example, don't use
10 * 3.14, use 10 * 3.25.  Want me to scour the tutorial for all such cases?

Or we could put the attached function at the start of the tutorial and use it
to format floats:

>>> f2ds(10 * 3.14)
'31400000000000002131628207280300557613372802734375e-48'
>>>

I'm sure newbies would feel assured by that <wink>.


def f2ds(x):
    """Return float x as exact decimal string.

    The string is of the form:
        "-", if and only if x is < 0.
        One or more decimal digits.  The last digit is not 0 unless x is 0.
        "e"
        The exponent, a (possibly signed) integer
    """

    import math
    # XXX ignoring infinities and NaNs for now.

    if x == 0:
        return "0e0"

    sign = ""
    if x < 0:
        sign = "-"
        x = -x

    f, e = math.frexp(x)
    assert 0.5 <= f < 1.0
    # x = f * 2**e exactly

    # Suck up CHUNK bits at a time; 28 is enough so that we suck
    # up all bits in 2 iterations for all known binary double-
    # precision formats, and small enough to fit in an int.
    CHUNK = 28
    top = 0L
    # invariant: x = (top + f) * 2**e exactly
    while f:
        f = math.ldexp(f, CHUNK)
        digit = int(f)
        assert digit >> CHUNK == 0
        top = (top << CHUNK) | digit
        f -= digit
        assert 0.0 <= f < 1.0
        e -= CHUNK
    assert top > 0

    # Now x = top * 2**e exactly.  Get rid of trailing 0 bits if e < 0
    # (purely to increase efficiency a little later -- this loop can
    # be removed without changing the result).
    while e < 0 and top & 1 == 0:
        top >>= 1
        e += 1

    # Transform this into an equal value top' * 10**e'.
    if e > 0:
        top <<= e
        e = 0
    elif e < 0:
        # Exact is top/2**-e.  Multiply top and bottom by 5**-e to
        # get top*5**-e/10**-e = top*5**-e * 10**e
        top *= 5L**-e

    # Nuke trailing (decimal) zeroes.
    while 1:
        assert top > 0
        newtop, rem = divmod(top, 10L)
        if rem:
            break
        top = newtop
        e += 1

    return "%s%de%d" % (sign, top, e)