on floating-point numbers

Chris Angelico rosuav at gmail.com
Sat Sep 4 23:38:03 EDT 2021


On Sun, Sep 5, 2021 at 1:04 PM Hope Rouselle <hrouselle at jevedi.com> wrote:
> The same question in other words --- what's a trivial way for the REPL
> to show me such cycles occur?
>
> >>>>>> 7.23.as_integer_ratio()
> >>> (2035064081618043, 281474976710656)
>
> Here's what I did on this case.  The REPL is telling me that
>
>   7.23 = 2035064081618043/281474976710656
>
> If that were true, then 7.23 * 281474976710656 would have to equal
> 2035064081618043.  So I typed:
>
> >>> 7.23 * 281474976710656
> 2035064081618043.0
>
> That agrees with the falsehood.  I'm getting no evidence of the problem.
>
> When take control of my life out of the hands of misleading computers, I
> calculate the sum:
>
>        844424930131968
>  +    5629499534213120
>     197032483697459200
>     ==================
>     203506408161804288
> =/= 203506408161804300
>
> How I can save the energy spent on manual verification?
>

What you've stumbled upon here is actually a neat elegance of
floating-point, and an often-forgotten fundamental of it: rounding
occurs exactly the same regardless of the scale. The number 7.23 is
represented with a certain mantissa, and multiplying it by some power
of two doesn't change the mantissa, only the exponent. So the rounding
happens exactly the same, and it comes out looking equal!

The easiest way, in Python, to probe this sort of thing is to use
either fractions.Fraction or decimal.Decimal. I prefer Fraction, since
a float is fundamentally a rational number, and you can easily see
what's happening. You can construct a Fraction from a string, and
it'll do what you would expect; or you can construct one from a float,
and it'll show you what that float truly represents.

It's often cleanest to print fractions out rather than just dumping
them to the console, since the str() of a fraction looks like a
fraction, but the repr() looks like a constructor call.

>>> Fraction(0.25)
Fraction(1, 4)
>>> Fraction(0.1)
Fraction(3602879701896397, 36028797018963968)

If it looks like the number you put in, it was perfectly
representable. If it looks like something of roughly that many digits,
it's probably not the number you started with.

ChrisA


More information about the Python-list mailing list