Reputation: 4397
In Python, the fractions.Fraction
and decimal.Decimal
standard library classes exist to help keep arithmetic with rational numbers precise. For the unfamiliar, an example of where it helps:
>>> 1 / 10 * 3
0.30000000000000004
>>> decimal.Decimal('1') / 10 * 3
Decimal('0.3')
>>> fractions.Fraction('1') / 10 * 3
Fraction(3, 10)
My question is, if I have a Fraction
, what's the best way to convert it to a Decimal
?
Unfortunately the obvious solution doesn't work:
>>> decimal.Decimal(fractions.Fraction(3, 10))
Traceback (most recent call last):
...
TypeError: conversion from Fraction to Decimal is not supported
Right now I'm using this code:
>>> decimal.Decimal(float(fractions.Fraction(3, 10)))
Decimal('0.299999999999999988897769753748434595763683319091796875')
Now, when I actually output this value, any amount of rounding will convert it to 0.3, and I only do this conversion immediately before output (all the core math is done with Fraction
). Still, it seems a bit silly to me that I can't get a Decimal('0.3')
from a Fraction(3, 10)
. Any help would be appreciated!
Upvotes: 19
Views: 4835
Reputation: 27629
You could insert an str
step, which produces a "nice" decimal string (likely exact, if you're actually dealing with "decimal numbers" like that 0.3, which you likely are if you're using decimal
).
import decimal, fractions
print(decimal.Decimal(str(float(fractions.Fraction(3, 10)))))
Output (Attempt This Online!):
0.3
Upvotes: 0
Reputation: 796
This is not quite a new answer, as Martijn's solution is still the best option, but instead a note about how a "proper" conversion method may be on the horizon.
Python 3.12 has introduced float-style formatting for Fraction
, which permits the following rather hacky function:
def decimal_from_fraction(frac):
return decimal.Decimal(f"{frac:.<prec>f}") # can also use `.g`
where <prec>
is the desired precision. It must unfortunately be fixed in the function, and to be useful exceed the precision of the global Decimal
context. Nonethless, the precision can be arbitrarily large.
All of this is to say that the bells and whistles for going straight to a Decimal
are more than present (up to traps and rounding), and some of the contested implementation details have been decided (with frankly little fanfare). Hopefully this story gets a happy ending soon.
Upvotes: 2
Reputation: 1123500
How about leaving the division of the fraction to Decimal()
itself?
def decimal_from_fraction(frac):
return frac.numerator / decimal.Decimal(frac.denominator)
This is what Fraction.__float__()
does (simply divide the numerator by the denominator), but by turning at least one of the two values into a Decimal
object you get to control the output.
This lets you use the decimal
context:
>>> decimal_from_fraction(fractions.Fraction(3, 10))
Decimal('0.3')
>>> decimal_from_fraction(fractions.Fraction(1, 55))
Decimal('0.01818181818181818181818181818')
>>> with decimal.localcontext() as ctx:
... ctx.prec = 4
... decimal_from_fraction(fractions.Fraction(1, 55))
...
Decimal('0.01818')
Upvotes: 23