[Tutor] Fraction - differing interpretations for number and string

Dave Angel davea at davea.name
Thu Apr 16 14:11:05 CEST 2015


On 04/16/2015 01:03 AM, Jim Mooney wrote:
> Why does Fraction interpret a number and string so differently? They come
> out the same, but it seems rather odd
>
>>>> from fractions import Fraction
>>>> Fraction(1.64)
> Fraction(7385903388887613, 4503599627370496)
>>>> Fraction("1.64")
> Fraction(41, 25)
>>>> 41/25
> 1.64
>>>> 7385903388887613 / 4503599627370496
> 1.64
>

When a number isn't an exact integer (and sometimes when the integer is 
large enough), some common computer number formats cannot store the 
number exactly.  Naturally we know about transcendentals, which cannot 
be stored exactly in any base.  PI and E and the square-root of two are 
three well known examples.

But even rational numbers cannot be stored exactly unless they happen to 
match the base you're using to store them.  For example, 1/3 cannot be 
stored exactly in any common base.  In decimal, it'd be a repeating set 
of 3's.  And whenever you stopped putting down threes, you've made an 
approximation.
     0.3333333333

Python defaults to using a float type, which is a binary floating point 
representation that uses the special hardware available in most recent 
computers.  And in fact, when you use a literal number in your source, 
it's converted to a float by the compiler, not stored as the digits you 
typed.

The number you specified in decimal, 1.64, is never going to be stored 
in a finite number of binary bits, in a float.

 >>> from fractions import Fraction
 >>> from decimal import Decimal

 >>> y = 1.64
Conversion to float appens at compile time, so the value given to y is 
already approximate.
    roughly equivalent to the following
 >>> y = float("1.64")

 >>> Fraction(y)
Fraction(7385903388887613, 4503599627370496)

If you converted it in string form instead to Decimal, then the number 
you entered would be saved exactly.

 >>> x = Decimal("1.64")
This value is stored exactly.

 >>> x
Decimal('1.64')

 >>> Fraction(x)
Fraction(41, 25)


Sometimes it's convenient to do the conversion in our head, as it were.
Since 1.64 is shorthand for  164/100, we can just pass those integers to 
Fraction, and get an exact answer again.

 >>> Fraction(164, 100)
Fraction(41, 25)


Nothing about this says that Decimal is necessarily better than float. 
It appears better because we enter values in decimal form and to use 
float, those have to be converted, and there's frequently a loss during 
conversion.  But Decimal is slower and takes more space, so most current 
languages use binary floating point instead.

I implemented the math on a machine 40 years ago where all user 
arithmetic was done in decimal floating point.  i thought it was a good 
idea at the time because of a principle I called "least surprise." 
There were roundoff errors, but only in places where you'd get the same 
ones doing it by hand.

History has decided differently.  When the IEEE committee first met, 
Intel already had its 8087 implemented, and many decisions were based on 
what that chip could do and couldn't do.  So that standard became the 
default standard that future implementations would use, whatever company.

-- 
DaveA


More information about the Tutor mailing list