[Tutor] Fraction - differing interpretations for number and string - presentation

Oscar Benjamin oscar.j.benjamin at gmail.com
Fri Apr 17 16:02:41 CEST 2015


On 17 April 2015 at 03:29, Steven D'Aprano <steve at pearwood.info> wrote:
> On Thu, Apr 16, 2015 at 03:11:59PM -0700, Jim Mooney wrote:
>
>> So the longer numerator and denominator would, indeed, be more accurate if
>> used in certain calculations rather than being normalized to a float - such
>> as in a Fortran subroutine or perhaps if exported to a machine with a
>> longer bit-length? That's mainly what I was interested in - if there is any
>> usable difference between the two results.

If it's okay to use float and then go onto machines with higher/lower
precision then the extra precision must be unnecessary in which case
why bother with the Fraction type? If you really need to transfer your
float from one program/machine to another then why not just send the
decimal representation. Your 64-bit float should be able to round-trip
to decimal text and back for any IEEE-754 compliant systems. Writing
it as a fraction doesn't gain any extra accuracy. (BTW Python the
language guarantees that float is always an IEEE-754 64-bit float on
any machine).

When you use floats the idea is that you're using fixed-width floating
point as an approximation of real numbers. You're expected to know
that there will be some rounding and not consider your computation to
be exact. So the difference between 1.64 and the nearest IEEE-754
64-bit binary float should be considered small enough not to worry
about.

It's possible that in your calculations you will also be using
functions from the math module such as sin, cos, etc. and these
functions cannot be computed *exactly* for an input such as 1.64.
However they can be computed up to any desired finite precision so we
can always get the nearest possible float which is what the math
module will do.

When you use the fractions module and the Fraction type the idea is
that floating point inexactness is unacceptable to you. You want to
perform *exact* arithmetic and convert from other numeric types or
text exactly. You won't be able to use functions like sin and cos but
that's no loss since you wouldn't be able to get an exact rational
result there anyway.

Because the Fractions module is designed for the "I want everything to
be exact" use case conversion from float to Fraction is performed
exactly. Conversion from string or Decimal to Fraction is also exact.
The Fraction will also display its exact value when printed and that's
what you're seeing.

If you really want higher accuracy than float consider ditching it
altogether in favour of Fraction which will compute everything you
want exactly. However there's no point in doing this if you're also
mixing floats into the calculations. float+Fraction coerces to float
discarding accuracy and then calculates with floating point rounding.
If that's acceptable then don't bother with Fraction in the first
place. If not make sure you stick to only using Fraction.

> You're asking simple questions that have complicated answers which
> probably won't be what you are looking for. But let's try :-)
>
> Let's calculate a number. The details don't matter:
>
> x = some_calculation(a, b)
> print(x)
>
> which prints 1.64. Great, we have a number that is the closest possible
> base 2 float to the decimal 164/100. If we convert that float to a
> fraction *exactly*, we get:
>
> py> Fraction(1.64)
> Fraction(7385903388887613, 4503599627370496)
>
> So the binary float which displays as 1.64 is *actually* equal to
> 7385903388887613/4503599627370496, which is just a tiny bit less than
> the decimal 1.64:
>
> py> Fraction(1.64) - Fraction("1.64")
> Fraction(-11, 112589990684262400)
>
> That's pretty close. The decimal value that normally displays as 1.64 is
> perhaps more accurately displayed as:
>
> py> "%.23f" % 1.64
> '1.63999999999999990230037'
>
> but even that is not exact.

Just to add to Steven's point. The easiest way to see the exact value
of a float in decimal format is:
>>> import decimal
>>> decimal.Decimal(1.64)
Decimal('1.6399999999999999023003738329862244427204132080078125')

> The reality is that very often, the difference
> isn't that important. The difference between
>
> 1.64 inches
>
> and
>
> 1.63999999999999990230037 inches
>
> is probably not going to matter, especially if you are cutting the
> timber with a chainsaw.

I once had a job surveying a building site as an engineer's assistant.
I'd be holding a reflector stick while he operated the laser sight
machine (EDM) from some distance away and spoke to me over the radio.
He'd say "mark it 10mm to the left". The marker spray would make a
circle that was about 100mm in diameter. Then I would get 4 unevenly
shaped stones (that happened to be lying around) and drop them on the
ground to mark out the corners of a 1500mm rectangle by eye.
Afterwards the excavator would dig a hole there using a bucket that
was about 1000mm wide. While digging the stones would get moved around
and the operator would end up just vaguely guessing where the original
marks had been.

The engineer was absolutely insistent that it had to be 10mm to the
left though. I think that just naturally happens when you're equipment
is more accurate than it needs to be for the task in hand.


Oscar


More information about the Tutor mailing list