3

What I'm looking for is a way to write out a python Fraction() type to an arbitrary number of decimal places. I've looked at the python docs for Fraction and Decimal, but I can't see any way to convert or to write out the Fraction.

So what I'm looking for is some way to print out

Fraction(5, 7)

as

0.7142857142857142857142857142857142857142857142857142

instead of

>> float(Fraction(5, 7))
0.7142857142857143

Specifying the number of DPs.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Jack TC
  • 344
  • 1
  • 5
  • 17

4 Answers4

6

You can have arbitrary precision using the Decimal class.

from fractions import Fraction
from decimal import localcontext, Decimal

def print_fraction(f, digits):
    assert(f.imag == 0)

    # Automatically reset the Decimal settings
    with localcontext() as ctx:
        ctx.prec = digits
        print(Decimal(f.numerator) / Decimal(f.denominator))

f = Fraction(5, 7)
print_fraction(f, 52)

This will print 0.7142857142857142857142857142857142857142857142857143.

AndiDog
  • 68,631
  • 21
  • 159
  • 205
  • +1. I do find it interesting that `Decimal(f)` does not work directly. Any idea why not? – Tim Pietzcker Jun 22 '12 at 11:12
  • Won't that division convert both the decimals to floats, losing precision? – Jack TC Jun 22 '12 at 11:16
  • @JackTC It won't lose precision as AndiDog is taking the numerator and denominator as Decimals. – Jon Clements Jun 22 '12 at 11:30
  • I like this answer - the only change I would make is `from decimal import localcontext` and inside `print_fraction` insert a `with localcontext() as c` - then at the end of `print_fraction` the .prec will automatically be restored - This refers to "# Note: You may want to reset this value aftwards" – Jon Clements Jun 22 '12 at 11:34
  • 1
    @TimPietzcker actually that's a good point - it might be worth asking Mark Dickinson who's the author of the library - he's quite often on the Python development or user lists. I can't see a reason why Decimal can't take a Fraction and do what AndiDog has done here... – Jon Clements Jun 22 '12 at 11:36
  • I've posted [the same answer](http://stackoverflow.com/a/11156418/4279) implemented slightly differently – jfs Jun 22 '12 at 12:50
  • @JonClements: Disclaimer: I'm *not* the author of the decimal library; just one of the many people who's put significant work into it at some point in the past. :-) – Mark Dickinson Jun 22 '12 at 13:28
  • 4
    @TimPietzcker: It would be a break with the way that the `Decimal` constructor currently works: at the moment, all constructions (from float, from string, from int, from a tuple) are exact, doing no rounding and making no use of the current precision or rounding mode. Construction from Fraction would have to take rounding into account. Then there are the thorny questions of whether it's okay that `Decimal(n) != Decimal(Fraction(n))` for large integers `n`. So implicit construction from Fraction is probably best avoided. – Mark Dickinson Jun 22 '12 at 13:39
  • @MarkDickinson Apologies if I mis-represented you as the "author" of the library - it's just when it comes to Decimal I just think "Mark Dickinson" - as you're the most active on lists I've seen regarding the topic :) – Jon Clements Jun 22 '12 at 14:06
  • @JonClements: No problem! I just wanted to avoid taking credit / blame for other people's work. :-) – Mark Dickinson Jun 22 '12 at 15:20
  • @JonClements: Thanks for the hint on `localcontext`, I knew something like this must exist. Edited my answer. – AndiDog Jun 22 '12 at 22:53
2
from decimal   import Decimal, localcontext
from fractions import Fraction

def format_fraction(f, precision):
    with localcontext() as ctx:
        ctx.prec = precision
        return str(Decimal(f.numerator) / f.denominator)

f = Fraction(5, 7)
print(format_fraction(f, 52))
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • How is this any different from the above? And shouldn't the denominator be converted to Decimal too? – Jack TC Jun 24 '12 at 00:37
  • @JackTC: There were 3 differences (the most important one was `localcontext()` (see editing history of the other question)). There is no need to convert the denominator to Decimal, try it. – jfs Jun 24 '12 at 11:34
  • It seems that the precision is specifying the total number of digits, and not the decimal digits after the point. `format_fraction(Fraction( 88554379,66000),2) == '1.3E+3'`. How can I make it return `'1341.73'`? – Janus Troelsen Sep 17 '13 at 19:26
0
def format_fraction(f, precision):
    if f == 0: return "0"
    s = str(int(f)) + "."
    f -= int(f)
    with localcontext() as ctx:
        ctx.prec = precision
        s += str(Decimal(f.numerator) / f.denominator).partition(".")[2]
    return s

This will return "1341.73" instead of "1.3E+3" on format_fraction(Fraction(88554379,66000),2).

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196
  • This will not work for negative numbers slightly below 0: `format_fraction(Fraction(-5, 11), 2)` prints `0.45`. I think nowadays one would use [Decimal.quantize](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) for this. – dennis May 24 '22 at 15:26
-3

You can do it with string formatting. And with that you can always write your own wrapper function.

>>> '%.64f' % (5 / 7.0)
'0.7142857142857143015746146375022362917661666870117187500000000000'
Christian Witts
  • 11,375
  • 1
  • 33
  • 46