Reputation: 981
I am currently implementing a Fraction
class
for training my OOP skills, and I have got to a problem... In my class, you are able to do math
operations between:
And I want to add both:
Since when I work with integers, what I do is to make them a Fraction
with denominator 1, I want to, when operating with float
, creating a Fraction
that represents that given float
. And that is where I have my problem.
Firstly, the minimum code required to understand my Fraction
class
:
class Fraction(object):
def __init__(self,num,den=1,reduce=True):
# only accept integers or convertable strings
if not(type(num) == int and type(den) == int):
if type(num) == str:
try:
num = int(num)
except ValueError:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
else:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
if type(den) == str:
try:
den = int(den)
except ValueError:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
else:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
# don't accept fractions with denominator 0
if den == 0:
raise ZeroDivisionError("The denominator must not be 0")
# if both num and den are negative, flip both
if num < 0 and den < 0:
num = abs(num)
den = abs(num)
# if only the den is negative, change the "-" to the numerator
elif den < 0:
num *= -1
den = abs(den)
self.num = num
self.den = den
# the self.auto is a variable that will tell us if we are supposed to
#automatically reduce the Fraction to its lower terms. when doing some
#maths, if either one of the fractions has self.auto==False, the result
#will also have self.auto==False
self.auto = reduce
if self.auto:
self.reduce()
def float_to_fraction(f):
'''should not be called by an instance of a Fraction, since it does not\
accept, purposedly, the "self" argument. Instead, call it as\
Fraction.float_to_fraction to create a new Fraction with a given float'''
# Start by making the number a string
f = str(f)
exp = ""
# If the number has an exponent (is multiplied by 10 to the power of sth
#store it for later.
if "e" in f:
# Get the "e+N" or "e-N"
exp = f[f.index("e"):]
# Slice the exponent from the string
f = f[:f.index("e")]
# Start the numerator and the denominator
num = "0"
den = "1"
# Variable to check if we passed a ".", marking the decimal part of a
#number
decimal = False
for char in f:
if char != ".":
# Add the current char to the numerator
num += char
if decimal:
# If we are to the right of the ".", also add a 0 to the
#denominator to keep proportion
den += "0"
# Slice parsed character
f = f[1:]
if char == ".":
# Tell the function we are now going to the decimal part of the
#float.
decimal = True
# Slice the "."
f = f[1:]
# Parse the exponent, if there is one
if exp != "":
# If it is a negative exponent, we should make the denominator bigger
if exp[1] == "-":
# Add as many 0s to the den as the absolute value of what is to
#the right of the "-" sign. e.g.: if exp = "e-12", add 12 zeros
den += "0"*int(exp[2:])
# Same stuff as above, but in the numerator
if exp[1] == "+":
num += "0"*int(exp[2:])
# Last, return the Fraction object with the parsed num and den!
return Fraction(int(num),int(den))
My float_to_fraction()
function converts, 100% accurately, a given float
to a Fraction
. But as I remember from my math classes a cyclic decimal with a n-digit long cycle, like 0.123123123123
... or 0.(123)
can be written in the form of a fraction with numerator = cycle
and denominator = (as many 9s as the length of the cycle)
:
123/999 = 0.(123)
3/9 (=1/3) = 0.(3); 142857/999999 (=1/7) = 0.(142857)
etc, etc...
But with this implementation, if I pass to the float_to_fraction()
an argument like 1/3, it will parse "0.3333333333333333" which is finite, returning this Fraction: 3333333333333333/10000000000000000
. It IS accurate, since I passed to the function a finite number! How can I implement, in this function, a way of recognizing cyclic decimals, so I can return, instead of a Fraction
with a denominator = 10^n
, a denominator with loads of 9s.
Upvotes: 0
Views: 360
Reputation: 26040
The best way to convert a decimal expression into an approximating rational is via the continued fraction expansion.
For x=0.123123123123 this results in
r=x,
a[0]=floor(r)=0, r=r-a[0], r=1/r=8.121951219520,
a[1]=floor(r)=8, r=r-a[1], r=1/r=8.199999999453,
a[2]=floor(r)=8, r=r-a[2], r=1/r=5.000000013653,
a[3]=floor(r)=5, r=r-a[3], r=1/r=73243975.48780,
And at this point r-a[3]<1e-5
the iteration stops. The rational approximation found is
x=1/(8+1/(8+1/5))=1/(8+5/41)=41/333 (=123/999)
The intermediate convergents are
1/8=0.125, x- 1/8 = -0.001876876877,
1/(8+1/8)=8/65, x- 8/65 = 0.0000462000460,
41/333, x-41/333 = -1.231231231231e-13.
Upvotes: 0