[Edu-sig] Long Integer Fractions

Kirby Urner pdx4d@teleport.com
Wed, 17 May 2000 08:00:11 -0700


On Fri, 12 May 2000 23:58:05 -0700 I wrote:
>Anyway, I wrote a Fraction class that sort of does 
>this (computes with fractions) -- maybe I'm reinventing 
>a wheel or two here.

Here's the code.  Sorta rough.  

Also, depends on primes.py, which is zipped inside of 
python101.zip and linked from my math-thru-programming 
essay at:

  http://www.inetarena.com/~pdx4d/ocn/numeracy2.html

I'll probably just end up sticking the Fraction class
inside of primes.py eventually, as one more example 
application of its methods (gcd and lcm, which depend
on getfactor which depends on isprime).  

The class has its limitations i.e. even though it deals 
with long integers, my getfactor method relies on prime
factorizations, and this gets impractical using the brute 
force "trial by division" algorithm I'm using (I feature 
some other prime tests, such as Euler's and Fermat's,
but they all have loop holes).

Could be this Fraction idea is already in some module,
but I haven't seen it yet, nor in the books I've looked
at.  As I recall, SmallTalk has native integer fractions.
Scheme too?

Given my "math through programming" focus, having the 
capability to do basic ops with long integer fractions 
might come in handy.

Part of the idea is kids are learning Python as part of 
their basic math instruction.  So they'll be able to 
see/read/comprehend the technique for dividing one 
fraction by another as:

    def __div__(self,n):
        # divide self by another fraction
        f = self.mkfract(n)
        recip = Fraction(f.denom,f.numer)
        return self.__mul__(recip)

In other words, whereas a pre-Python conventional text might 
say "multiply by the reciprocal", here we see the __mul__ 
method being invoked, after creating Fraction recip with 
denom and numer reversed.

One of the things traditionalists scream about when reading
reformist math texts is that "dividing fractions" seems 
to be given short shrift.  So these folks should be happy
to see that I'm bringing it back -- albiet in a different
notation (self-executing).

========================= roots.py =================

import primes

class Fraction:
    numerator   = 1L
    denominator = 1L

    def mkfract(self,n):
        # this method is to allow ops that mix Fractions
        # with non-decimal numbers e.g. 25 * (3/7)
        if type(n).__name__ in ['int','float','long int']:
            return Fraction(n)
        else: return n

    def __init__(self,num=1L,den=1L):
        self.numer = long(num)
        self.denom = long(den)
        self.simplify()

    def __mul__(self,n):
        f = self.mkfract(n)
        return Fraction(self.numer*f.numer,
                        self.denom*f.denom)
    
    def __div__(self,n):
        # divide self by another fraction
        f = self.mkfract(n)
        recip = Fraction(f.denom,f.numer)
        return self.__mul__(recip)
    
    def simplify(self):
        # reduce numerator/denominator to lowest terms
        divisor = primes.gcd(abs(self.numer),abs(self.denom))
        if divisor > 1:
            self.numer = self.numer/divisor
            self.denom = self.denom/divisor
            
    def __add__(self,n):
        # add self to another fraction
        f = self.mkfract(n)
        common = primes.lcm(self.denom,f.denom)
        sum = self.numer * common/self.denom
        sum = sum + f.numer * common/f.denom
        return Fraction(sum,common)

    def __sub__(self,n):
        # subtract another fraction from self
        return self.__add__(-n)

    def __neg__(self):
        # negate self
        return Fraction(-self.numer,self.denom)
    
    __rmul__ = __mul__

    def str(self):
        return str(self.numer)+"/"+str(self.denom)
    
    def list(self):
        return [self.numer,self.denom]
    
    def float(self):
        return (self.numer*1.0)/(self.denom*1.0)

Fraction in action (this version auto-simplifies):

 Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
 Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
 IDLE 0.5 -- press F1 for help
 >>> from roots import Fraction
 >>> a = Fraction(2,3)
 >>> b = Fraction(3,2)
 >>> c = a+b
 >>> c.str()   # str() is for seeing as string, nnn/ddd format
 '13L/6L'
 >>> c.float() # ...or you can output the decimal equivalent with float()
 2.16666666667
 >>> c=a-b
 >>> c.str()
 '-5L/6L'
 >>> a = Fraction(300,10)
 >>> a.str()  # note autosimplification, i.e. 300/10 is now 30/1
 '30L/1L'
 >>> (a*b).str()  
 '45L/1L'
 >>> d=25*b       # integer x Fraction OK
 >>> d.str()
 '75L/2L'

Kirby

PS:  thanks to Stan Heckman for catching this typo:

Fri May 12 22:28:33 2000

>>> halley(1000,4)  # 4th root of 1000 (duh)
10.0

Shoulda been 

>>> halley(10000,4)  # 4th root of 1000 (duh)
10.0

(double duh)