Fun with Python fractions

⅝

One of the most under-appreciated packages in Python is the fractions package. I already mentioned in this blog that floating points are a good way to shoot yourself in the foot, and rational fractions is often the saner alternative.

Basically this package offers a Fraction class that represents a rational number with two integers, a numerator and a denominator, like you learnt in school.

a = fractions.Fraction(1, 3)
a → Fraction(1, 3)
str(a) → ⅓
float(a) → 0.3333333333333333
a * 3 → Fraction(1, 1)
a * 3 == 1 → True
a + 3 → Fraction(10, 3)

So far, so good. Now what happens when you convert a float into a fraction.

b = fractions.Fraction(0.3)
b → Fraction(5404319552844595, 18014398509481984)

What happened here? The fraction representing 0.3 should be fractions.Fraction(3, 10)! This is the floating point representation roaring its head, the fraction does not represent 0.3, but the floating point representation of 0.3. That conversion is actually exact. Let’s dig a bit more.

hex(b.numerator) → '0x13333333333333'
hex(b.denominator) → '0x40000000000000'
c = fractions.Fraction(3, 10) - b 
c → Fraction(1, 90071992547409920)
hex(c.denominator) → '0x140000000000000'

But you can force a more compact representation using the limit_denominator method.

fractions.Fraction(0.3).limit_denominator(100) → Fraction(3, 10)

Now, of course, you can’t convert all floating point values to fractions.

fractions.Fraction(float('Inf')) 
  → OverflowError: cannot convert Infinity to integer ratio

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: