How to simplify a fraction efficiently?
Question:
I’m writing my own floating point number class, which saves numbers as a fraction of two numbers.
I’ve made 3 different functions to simplify the fraction, but they take all too much time.
The class has two attributes, up and down, to represent the upper and lower number of the fraction.
preems = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229]
class fractionnum:
def __init__(self,up,down):
self.up=up
self.down=down
def simplify11(self):
thmax = max(self.up, self.down) // 2
for x in preems:
if x>thmax or 1 in (self.up,self.down):
break
while self.up%x==self.down%x==0:
self.up//=x
self.down//=x
def simplify2(self):
def isdivbyup(preem):
return not self.up%preem
def isdivbydown(preem):
return not self.down%preem
def divtime(preem):
#down=self.down
while not self.up % preem and not self.down %preem:
self.up //= preem
self.down//=preem
downfacts=list(filter(isdivbydown,preems))
upfacts = list(filter(isdivbyup, preems))
genfacts=[_ for _ in downfacts if _ in upfacts]
[divtime(x) for x in genfacts]
def simplify3(self):
up=self.up
down=self.down
while down!=0: #euclidian algorithm
prevup=up
up=down
down=prevup%down
self.up//=up
self.down//=up
Currently, these three functions take almost the same average time to execute: 0.034130303 seconds.
The function is called after every calculation made. A sine function with 20 iterations takes 1974 execution times.
How can I make the function run more efficiently?
Answers:
As it is mentioned in the comments, rational numbers are already implemented by various Python libraries (fractions
, sympy
etc.). I assume though that you want to do it on your own as an exercise. In such case, your methods simplify11()
and simplify2()
are not good, since they rely on a precomputed list of prime numbers and will give wrong results for fractions where denominator and numerator have a prime factor which is not among these primes. On top of it, simplify2()
uses a list comprehension [divtime(x) for x in genfacts]
solely for its side effects, which is both inefficient and a very bad practice.
simplify3()
is better, but it does not implement the Euclidean algorithm correctly. It should be something like this:
def simplify3(self):
if self.up == 0:
self.down = 1
else:
u, d = abs(self.up), abs(self.down)
if u < d:
d, u = u, d
while d != 0:
u, d = d, u % d
self.up //= u
self.down //= u
This assumes that your class makes sure that self.down
is not equal to 0.
It is of course simpler to use the gcd()
function from the math
module:
from math import gcd
def simplify3(up, down):
common = gcd(self.up, self.down)
self.up //= common
self.down //= common
I’m writing my own floating point number class, which saves numbers as a fraction of two numbers.
I’ve made 3 different functions to simplify the fraction, but they take all too much time.
The class has two attributes, up and down, to represent the upper and lower number of the fraction.
preems = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229]
class fractionnum:
def __init__(self,up,down):
self.up=up
self.down=down
def simplify11(self):
thmax = max(self.up, self.down) // 2
for x in preems:
if x>thmax or 1 in (self.up,self.down):
break
while self.up%x==self.down%x==0:
self.up//=x
self.down//=x
def simplify2(self):
def isdivbyup(preem):
return not self.up%preem
def isdivbydown(preem):
return not self.down%preem
def divtime(preem):
#down=self.down
while not self.up % preem and not self.down %preem:
self.up //= preem
self.down//=preem
downfacts=list(filter(isdivbydown,preems))
upfacts = list(filter(isdivbyup, preems))
genfacts=[_ for _ in downfacts if _ in upfacts]
[divtime(x) for x in genfacts]
def simplify3(self):
up=self.up
down=self.down
while down!=0: #euclidian algorithm
prevup=up
up=down
down=prevup%down
self.up//=up
self.down//=up
Currently, these three functions take almost the same average time to execute: 0.034130303 seconds.
The function is called after every calculation made. A sine function with 20 iterations takes 1974 execution times.
How can I make the function run more efficiently?
As it is mentioned in the comments, rational numbers are already implemented by various Python libraries (fractions
, sympy
etc.). I assume though that you want to do it on your own as an exercise. In such case, your methods simplify11()
and simplify2()
are not good, since they rely on a precomputed list of prime numbers and will give wrong results for fractions where denominator and numerator have a prime factor which is not among these primes. On top of it, simplify2()
uses a list comprehension [divtime(x) for x in genfacts]
solely for its side effects, which is both inefficient and a very bad practice.
simplify3()
is better, but it does not implement the Euclidean algorithm correctly. It should be something like this:
def simplify3(self):
if self.up == 0:
self.down = 1
else:
u, d = abs(self.up), abs(self.down)
if u < d:
d, u = u, d
while d != 0:
u, d = d, u % d
self.up //= u
self.down //= u
This assumes that your class makes sure that self.down
is not equal to 0.
It is of course simpler to use the gcd()
function from the math
module:
from math import gcd
def simplify3(up, down):
common = gcd(self.up, self.down)
self.up //= common
self.down //= common