119

I was wondering how to convert a decimal into a fraction in its lowest form in Python.

For example:

0.25  -> 1/4
0.5   -> 1/2
1.25  -> 5/4
3     -> 3/1
nbro
  • 15,395
  • 32
  • 113
  • 196
user2370460
  • 7,470
  • 10
  • 31
  • 46

6 Answers6

201

You have two options:

  1. Use float.as_integer_ratio():

    >>> (0.25).as_integer_ratio()
    (1, 4)
    

    (as of Python 3.6, you can do the same with a decimal.Decimal() object.)

  2. Use the fractions.Fraction() type:

    >>> from fractions import Fraction
    >>> Fraction(0.25)
    Fraction(1, 4)
    

The latter has a very helpful str() conversion:

>>> str(Fraction(0.25))
'1/4'
>>> print Fraction(0.25)
1/4

Because floating point values can be imprecise, you can end up with 'weird' fractions; limit the denominator to 'simplify' the fraction somewhat, with Fraction.limit_denominator():

>>> Fraction(0.185)
Fraction(3332663724254167, 18014398509481984)
>>> Fraction(0.185).limit_denominator()
Fraction(37, 200)

If you are using Python 2.6 still, then Fraction() doesn't yet support passing in a float directly, but you can combine the two techniques above into:

Fraction(*0.25.as_integer_ratio())

Or you can just use the Fraction.from_float() class method:

Fraction.from_float(0.25)

which essentially does the same thing, e.g. take the integer ratio tuple and pass that in as two separate arguments.

And a small demo with your sample values:

>>> for f in (0.25, 0.5, 1.25, 3.0):
...     print f.as_integer_ratio()
...     print repr(Fraction(f)), Fraction(f)
... 
(1, 4)
Fraction(1, 4) 1/4
(1, 2)
Fraction(1, 2) 1/2
(5, 4)
Fraction(5, 4) 5/4
(3, 1)
Fraction(3, 1) 3

Both the fractions module and the float.as_integer_ratio() method are new in Python 2.6.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Thanks! Options 1 works, but option 2 (Fraction(0.25)) gives me the error: "TypeError: 'float' object cannot be interpreted as an index". Can you tell me why? – user2370460 Apr 28 '14 at 14:57
  • 1
    @user2370460: What version of Python are you using? – Martijn Pieters Apr 28 '14 at 14:58
  • @MartijnPieters Python 2.6.1, on Mac OS X 10.6.8 – user2370460 Apr 28 '14 at 14:59
  • 5
    @user2370460: Ah, yes, only version 2.7 and up support passing in a float or decimal type; use `Fraction(*(0.25).as_integer_ratio())` instead in that case. – Martijn Pieters Apr 28 '14 at 15:02
  • 1
    Note, both the `fractions` module and `float.as_integer_ratio` were added in Python 2.6. If you're running an older version of Python these will not be available. – IceArdor Apr 28 '14 at 16:47
  • 3
    @user2370460: I totally managed to miss the available `Fraction.from_float()` class method, much clearer. `Fraction.from_float(0.25)`. – Martijn Pieters Apr 29 '14 at 00:38
  • How usable is `as_integer_ratio` in practice? To me it seems to be limited to values with powers of 2 in the denominator. – Wolf Jun 09 '21 at 10:08
  • @Wolf not sure what tests you did but `as_integer_ratio()` is reliable and exact. Note that floating point numbers are built up from binary fractions, and binary fractions by definition have 1 as the numerator and a power of two as the denominator. It would therefor be quite normal for their integer ratio to reflect that. – Martijn Pieters Jun 12 '21 at 21:22
  • @Wolf I don’t find the results surprising. If you feel the docs should be updated you’ll have to ask the Python team. – Martijn Pieters Jun 13 '21 at 00:32
15
from fractions import Fraction

print(Fraction(0.25))
print(Fraction(0.5))
print(Fraction(1.25))
print(Fraction(3))

#1/4
#1/2
#5/4
#3
Timothy Alexis Vass
  • 2,526
  • 2
  • 11
  • 30
ajknzhol
  • 6,322
  • 13
  • 45
  • 72
  • Thanks, but this gives me: "TypeError: 'float' object cannot be interpreted as an index". Can you tell me why? – user2370460 Apr 28 '14 at 14:51
  • 2
    Good suggestion, but I'd like to add: the one-argument constructor works well for some numbers, but not others. For instance, `Fraction(.2)` becomes `Fraction(3602879701896397, 18014398509481984)`. It might be better to use the two-argument constructor, ex. `Fraction(2,10)` – Kevin Apr 28 '14 at 14:54
  • 3
    @Kevin: that's because .2 cannot be accurately represented by a float. See my answer for a workaround; limit the denominator and you can bring back it back to 2/10 in a jiffy. – Martijn Pieters Apr 29 '14 at 00:32
12

To expand upon Martijn Pieters excellent answer with an additional option due to the imprecision inherent with more complex floats. For example:

>>> f = 0.8857097
>>> f.as_integer_ratio()
(1994440937439217, 2251799813685248)          # mathematically wrong
>>> Fraction(f)
Fraction(1994440937439217, 2251799813685248)  # same result but in a class
>>> Fraction(f).limit_denominator()
Fraction(871913, 984423)                      # still imprecise

The mathematical result desired was 8857097/10000000 which can be achieved by casting to a string and then manipulating it.

Edited Response

I found a much simpler way to resolve the accuracy issue.

>>> Fraction(str(f))
Fraction(8857097, 10000000)

Casting as to a string also allows for accurate Decimal instances

>>> Decimal(f).as_integer_ratio()
(1994440937439217, 2251799813685248)
>>> Decimal(str(f)).as_integer_ratio()
(8857097, 10000000)

Original Response

def float_to_ratio(flt):
    if int(flt) == flt:        # to prevent 3.0 -> 30/10
        return int(flt), 1
    flt_str = str(flt)
    flt_split = flt_str.split('.')
    numerator = int(''.join(flt_split))
    denominator = 10 ** len(flt_split[1])
    return numerator, denominator

Now let's test it:

>>> float_to_ratio(f)
(8857097, 10000000)      # mathematically correct

I will note that this kind of fraction precision is not optimized and will usually not be needed, but for completeness it is here. This function doesn't simplify the fraction, but you can do additional processing to reduce it:

>>> n = 0.5
>>> float_to_ratio(n)
(5, 10)
>>> Fraction(*float_to_ratio(n))
Fraction(1, 2)
Matt Eding
  • 917
  • 1
  • 8
  • 15
7

If you'd like to print a proper fraction, this little recipe should do:

from fractions import Fraction    

def dec_to_proper_frac(dec):
    sign = "-" if dec < 0 else ""
    frac = Fraction(abs(dec))
    return (f"{sign}{frac.numerator // frac.denominator} "
            f"{frac.numerator % frac.denominator}/{frac.denominator}")

This will print as follows:

>>> dec_to_proper_frac(3.75)
>>> "3 3/4"
The Aelfinn
  • 13,649
  • 2
  • 54
  • 45
1

This is how to do it simple and properly.

By using Fraction:

from fractions import Fraction
decimals = [0.25, 0.5, 1.25, 3, 0.6, 0.84]

for d in decimals:
    print(Fraction(str(d))) #Cast as string for proper fraction

By using Decimal:

from  decimal import Decimal
decimals = [0.25, 0.5, 1.25, 3, 0.6, 0.84]

for d in decimals:
    d = Decimal(str(d)) #Cast as string for proper fraction
    nominator,denominator = d.as_integer_ratio()
    if denominator==1:
        print(a)
    else:
        print(nominator,denominator, sep="/")

Output:

1/4
1/2
5/4
3
3/5
21/25
Timothy Alexis Vass
  • 2,526
  • 2
  • 11
  • 30
0

One of the easiest way is to use as_integer_ratio() like this.

b = 0.125

b.as_integer_ratio()

# Output as Tuple(1, 8).Numerator as 1 & Denominator as 8
Hadi Mir
  • 4,497
  • 2
  • 29
  • 31