Klasse für Brüche

Code-Stücke können hier veröffentlicht werden.
Antworten
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

Wegen der Diskussion um die Gleitkommazahlen, ahbe ich mal nen Klasse für Brüche geschrieben, die sich wie eine Zahl handhaben lässt. Zur Initialisierung gibt man den Zähler, den Nenner und einen Parameter an. Letzerer gibt an, ob bei Rufen von int oder long auch aufgerundet werden soll (ab .5), oder nur abgerundet werden soll, wie es int und long normalerweise tun.

Code: Alles auswählen

class bruch:
    def __init__(self,z=1,n=1,round=1):
        self.z,self.n=ifelse(n>=0,[z,n],[-z,-n])
        t = self.__ggt__(self.z,self.n)
        self.z,self.n=self.z/t,self.n/t
        if self.n==0:
            raise ZeroDivisionError, "integer division or modulo by zero"
        self.round=round
    def round(state=1):
        self.round=state
    def gettuple():
        return self.z,self.n
    
    def __ggt__(self,x,y):
        while y > 0:
            rest = x % y
            x = y
            y = rest
        return x
    def __kgv__(self,x,y):
        return (x/self.__ggt__(x,y))*y
    def __round__(self,x):
        return long(x+ 0.5 * ((x > 0) or -1))

    def __add__(self,other):
        zs = self.z * other.n + other.z * self.n
        ns = self.n * other.n
        t = self.__ggt__(zs,ns)
        return bruch(zs/t, ns/t)
    def __div__(self,other):
        zs = self.z * other.n
        ns = self.n * other.z
        t = self.__ggt__(zs,ns)
        return bruch(zs/t, ns/t)
    def __divmod__(self,other):
        return (self/other,self%other)
    def __mod__(self,other):
        if other==0 or other.z==0:
            raise ZeroDivisionError, "integer division or modulo by zero"
        ns=self.__kgv__(self.n,other.n)
        self.z=self.z*(ns/self.n)
        other.z=other.z*(ns/other.n)
        return bruch(self.z%other.z,ns)
    def __mul__(self,other):
        zs = self.z * other.z
        ns = self.n * other.n
        t = self.__ggt__(zs,ns)
        return bruch(zs/t, ns/t)
    def __pow__(self,other):
        other=long(other)
        if other>=0:
            return bruch(self.z**other,self.n**other)
        else:
            return bruch(self.n**other,self.z**other)
    def __sub__(self,other):
        zs = self.z * other.n - other.z * self.n
        ns = self.n * other.n
        t = self.__ggt__(zs,ns)
        return bruch(zs/t, ns/t)

    def __abs__(self):
        return bruch(abs(self.z),abs(self.n))
    def __float__(self):
        return float(self.z)/float(self.n)
    def __int__(self):
        return int(self.__long__())
    def __long__(self):
        if self.n==1:
            return long(self.z)
        elif self.round:
            return self.__round__(float(self.z)/float(self.n))
        else:
            return long(float(self.z)/float(self.n))
    def __neg__(self):
        return bruch(-self.z,self.n)
    def __pos__(self):
        return bruch(self.z,self.n)
    def __repr__(self):
        return "bruch(%i,%i)"%(self.z,self.n)
    def __str__(self):
        return "(%i / %i)"%(self.z,self.n)
Hat aber noch ein paar Mängel: man kann zwar ohne Probleme rechnen, aber sie verträgt sich nicht mit Integern in dieser Form:

Code: Alles auswählen

>>> x=bruch(1,3)
>>> x
bruch(1,3)
>>> x*bruch(3,1)
bruch(1,1)
>>> x*3
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in ?
    x*3
  File "C:\Python22\lib\site-packages\utils.py", line 77, in __mul__
    zs = self.z * other.z
AttributeError: 'int' object has no attribute 'z'
Wie kann ich der Klasse sagen, sie soll prüfen, ob das andere ne Instanz von Bruch ist, oder eine normale Zahl ? (welche sich ja als bruch(x,1) darstellen ließe)
Voges
User
Beiträge: 564
Registriert: Dienstag 6. August 2002, 14:52
Wohnort: Region Hannover

Hallo!

Ungetestet:

Code: Alles auswählen

def _forceBruch(self,wert):
    if type(wert) == int: return bruch(wert,1)
    else                  return wert

def __mul__(self,other):
    other = self._forceBruch(other)
    zs = self.z * other.z
    ns = self.n * other.n
    t = self.__ggt__(zs,ns)
    return bruch(zs/t, ns/t)
BTW: Wie sieht denn die Funktion ifelse() aus?

Jan
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

Code: Alles auswählen

def ifelse(bedingung,true,false):
    return (bedingung and [true] or [false])[0]
hmm, keine so schlechte Idee, aber ich bau noch eine Abfrage für longs und floats ein, die dann gerundet werden...
Voges
User
Beiträge: 564
Registriert: Dienstag 6. August 2002, 14:52
Wohnort: Region Hannover

Mir fällt gerade noch was auf. Diese __bla__-Schreibweise ist doch nur für diese "Special Methods" gedacht, also für z.B. die Operatorenüberladung, oder? Wenn es Dir um diese Quasi-private-Deklaration geht, funktioniert das nur ohne die anhängenden "__".
Jan
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

hab gerade mal wieder die Klasse gebraucht und noch mal überarbeitet. Sie dürfte jetzt so ziemlich alles unterstützen, was von Zahlen erwartet werden kann... also bis auf die Instanzierung wie z.B. Integer einsetzten.

Code: Alles auswählen

class BruchError(Exception):
    pass

class Bruch:
    def __init__(self,z=1,n=1,round=1):
        z,n=map(long,(z,n))
        if n>=0:
            self.z,self.n=z,n
        else:
            self.z,self.n=[-z,-n]
        t = self._ggt(self.z,self.n)
        self.z,self.n=self.z/t,self.n/t
        if self.n==0:
            raise ZeroDivisionError, "integer division or modulo by zero"
        self.round=round
    def set_roundstate(self,state=1):
        """Setzt den Status, ob beim rechnen mit Gleitkommazahlen gerundet werden soll"""
        self.round=state
    def gettuple(self):
        """Gibt das Tupel (Zaehler,Nenner) zureuck"""
        return self.z,self.n
    
    def _forceBruch(self,wert):
        if isinstance(wert,Bruch):
            return wert
        elif type(wert) in (int,long):
            return Bruch(wert,1)
        elif type(wert) == float:
            if self.round:
                return Bruch(self._round(wert),1)
            else:
                return Bruch(long(wert),1)
        else:
            raise BruchError,"no compatiple type for operation"
    def _ggt(self,x,y):
        while y > 0:
            rest = x % y
            x = y
            y = rest
        return x
    def _kgv(self,x,y):
        return (x/self._ggt(x,y))*y
    def _round(self,x):
        return long(x+ 0.5 * ((x > 0) or -1))

    def __add__(self,other):
        other = self._forceBruch(other)
        zs = self.z * other.n + other.z * self.n
        ns = self.n * other.n
        t = self._ggt(zs,ns)
        return Bruch(zs/t, ns/t)
    def __cmp__(self,other):
        try:
            other = self._forceBruch(other)
        except BruchError:
            return cmp(long(self),other)
        else:
            kgv=self._kgv(self.n,other.n)
            zself=self.z*(kgv/self.n)
            zother=other.z*(kgv/other.n)
            if zself < zother:
                return -1
            elif zself == zother:
                return 0
            else:
                return 1
    def __div__(self,other):
        other = self._forceBruch(other)
        if other.z==0L:
            raise ZeroDivisionError, "integer division or modulo by zero"
        zs = self.z * other.n
        ns = self.n * other.z
        t = self._ggt(zs,ns)
        return Bruch(zs/t, ns/t)
    def __divmod__(self,other):
        other = self._forceBruch(other)
        return (self.__div__(other),self.__mod__(other))
    def __mod__(self,other):
        other = self._forceBruch(other)
        if other.z==0:
            raise BruchError, "integer division or modulo by zero"
        ns=self._kgv(self.n,other.n)
        zself=self.z*(ns/self.n)
        zother=other.z*(ns/other.n)
        return Bruch(zself%zother,ns)
    def __mul__(self,other):
        other = self._forceBruch(other)
        if other.n==0L:
            raise ZeroDivisionError, "integer division or modulo by zero"
        zs = self.z * other.z
        ns = self.n * other.n
        t = self._ggt(zs,ns)
        return Bruch(zs/t, ns/t)
    def __pow__(self,other,z=None):
        other = self._forceBruch(other)
        if other.n!=1:
            raise BruchError, "pow excepts only integers or Bruch-instances that represent them"
        if other.z>=0:
            erg=Bruch(self.z**other.z,self.n**other.z)
        else:
            erg=Bruch(self.n**(-other.z),self.z**(-other.z))
        if z==None:
            return erg
        else:
            return erg%z
    def __radd__(self,other):
        return self.__add__(other)
    def __rdiv__(self,other):
        other = self._forceBruch(other)
        if self.z==0L:
            raise ZeroDivisionError, "integer division or modulo by zero"
        zs = other.z * self.n
        ns = other.n * self.z
        t = self._ggt(zs,ns)
        return Bruch(zs/t, ns/t)
    def __rdivmod__(self,other):
        return (self.__rdiv__(other),self.__rmod__(other))
    def __rmod__(self,other):
        other = self._forceBruch(other)
        if self.z==0:
            raise BruchError, "integer division or modulo by zero"
        ns=self._kgv(self.n,other.n)
        zself=self.z*(ns/self.n)
        zother=other.z*(ns/other.n)
        return Bruch(zother%zself,ns)
    def __rmul__(self,other):
        return self.__mul__(other)
    def __rpow__(self,other,z=None):
        if z==None:
            return other**float(self)
        else:
            return (other**float(self))%z
    def __rsub__(self,other):
        other = self._forceBruch(other)
        zs = other.z * self.n - self.z * other.n
        ns = other.n * self.n
        t = self._ggt(zs,ns)
        return Bruch(zs/t, ns/t)
    def __sub__(self,other):
        other = self._forceBruch(other)
        zs = self.z * other.n - other.z * self.n
        ns = self.n * other.n
        t = self._ggt(zs,ns)
        return Bruch(zs/t, ns/t)

    def __abs__(self):
        return Bruch(abs(self.z),abs(self.n))
    def __float__(self):
        return float(self.z)/float(self.n)
    def __int__(self):
        return int(self.__long__())
    def __long__(self):
        if self.n==1:
            return long(self.z)
        elif self.round:
            return self._round(float(self.z)/float(self.n))
        else:
            return long(float(self.z)/float(self.n))
    def __neg__(self):
        return Bruch(-self.z,self.n)
    def __pos__(self):
        return Bruch(self.z,self.n)
    def __repr__(self):
        return "Bruch(%i,%i)"%(self.z,self.n)
    def __str__(self):
        return "(%i / %i)"%(self.z,self.n)
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

hmm... hab doch noch ein Problem: ist es möglich, floats in Brüche diese Art umzuwandeln? Ich ziele auf die Methode _forceBruch ab und würde zum Beispiel gerne 0.5 als Bruch(1,2) erkennen...

Ich hab mir überlegt, dass man das näherungsweise z.B. so darstellen könnte, aber geht das noch anderes, wenn möglich genauer?

Code: Alles auswählen

Bruch(long(float*100000),100000)
joerg
User
Beiträge: 188
Registriert: Samstag 17. August 2002, 17:48
Wohnort: Berlin
Kontaktdaten:

Nur so aus dem Kopf:
Ich würde aus dem float mittels repr() einen String machen. Wenn die Schreibweise mit Exponent ist (1.234e+5), lassen wir das 'e' und alles danach erstmal weg.

Dann schaue ich mir an, wieviel Nachkommastellen x es gibt (Anzahl der Zeichen nach einem '.'). Dann lasse ich den Punkt einfach weg, nehme die resultierende Zahl als Zähler und 10^x als Nenner.

Falls im String ein Exponent vorkam, müssen wir den jetzt noch berücksichtigen, durch geeignete Behandlung des Nenners (und evetuell des Zählers, falls die Stellen des Nenners nicht ausreichen).

Schließlich darf noch gekürzt werden, damit aus 0.5 -> 5 / 10 auch wirklich 1 / 2 wird.

repr() hat gegenüber str() den Vorteil, daß die Genauigkeit des Wertes wirklich so wiedergegeben wird, also eval(repr(x)) == x zutrifft.

Viel Glück!
Jörg
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

hmm... :roll: daran hab ich noch nicht gedacht, das könnte gut hinhauen. Gekürzt wird aber schon automatisch in der init.

thx, Milan
joerg
User
Beiträge: 188
Registriert: Samstag 17. August 2002, 17:48
Wohnort: Berlin
Kontaktdaten:

Milan hat geschrieben:hmm... :roll: daran hab ich noch nicht gedacht, das könnte gut hinhauen. Gekürzt wird aber schon automatisch in der init.

thx, Milan
Ich habe es mal probiert... sieht schrecklich aus und ist nicht dokumentiert, aber scheint zu funktionieren! ;-)

Code: Alles auswählen

def float2bruch(f):
    Ne = 0
    r = repr(f)
    if 'e' in r:
        x, e = r.split('e')
    else:
        x = r
        e = None
    if '.' in x:
        Ne = len(x)-x.index('.')-1
        x = x.replace('.', '')
    Z = long(x)
    if e:
        e = int(e)
        if e < 0:
            Ne += -e
        else:
            if e < Ne:
                Ne -= e
            else:
                Z *= 10**(e-Ne)
                Ne = 0
    return Z, 10**Ne

for i in range(-15, 15):
    x = 1.234567 * 10**i
    y = float2bruch(x)
    z = float(y[0])/y[1]
    print repr(x), '->', y, '->', repr(z)
for x in ( 1.23, 1.5e+200, 0.000005):
    y = float2bruch(x)
    z = float(y[0])/y[1]
    print repr(x), '->', y, '->', repr(z)
float2bruch() nimmt einen Float und gibt ein Tupel (Zähler, Nenner) zurück. Der Bruch kann noch kürzbar sein durch Zahlen kleiner als 10.

Für alle meine Beispiele klappte es bisher...

Jörg
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

ich hab auch so meine Version, da deine aber ohne RE ist, ist wohl die Laufzeit geringer... ich vereinfache noch nen bissl und baus dann ein.

Code: Alles auswählen

import re
def getnums(f=1.0):
    temp=re.match('(\d*)\.(\d*)e?([\+-]?)(\d{0,3})',repr(f)).groups()
    z=long(temp[0])*(10L**len(temp[1]))+long(temp[1])
    n=10L**len(temp[1])
    if temp[2]=='+':
        z*=10L**long(temp[3])
    if temp[2]=='-':
        n*=10L**long(temp[3])
    return z,n
Milan
User
Beiträge: 1078
Registriert: Mittwoch 16. Oktober 2002, 20:52

So, die wahrscheinlich endgültige Version (falls nicht noch irgendein Fehler auftritt)... Ich hab nen paar Geschwindigkeitstest gemacht, aber so sehr ich auch an deinem Script rumgebastelt hab (habs auch noch mal ganz neu versucht), es war immer langsamer als meins. Deswegen ist es jetzt mit Regex implementiert:

Code: Alles auswählen

import re

class Bruch(object):
    __slots__=["_float_regex","z","n"]
    _float_regex=re.compile('(\d*)\.(\d*)e?([\+-]?)(\d{0,3})')
    
    def __init__(self,z=1,n=1):
        if isinstance(z,Bruch) or isinstance(n,Bruch):
            z,n=(z/n).gettuple()
        else:
            z,n=map(int,(z,n))
        if n>=0:
            self.z,self.n=z,n
        else:
            self.z,self.n=[-z,-n]
        t = self._ggt(self.z,self.n)
        self.z,self.n=self.z/t,self.n/t
        if self.n==0:
            raise ZeroDivisionError, "integer division or modulo by zero"
    def gettuple(self):
        """Gibt das Tupel (Zaehler,Nenner) zureuck"""
        return self.z,self.n
    
    def _forceBruch(self,wert):
        if isinstance(wert,Bruch):
            return wert
        elif (type(wert) in (int,long)) or (wert % 1 == 0):
            return Bruch(wert,1)
        elif type(wert) == float:
            temp=self._float_regex.match(repr(wert)).groups()
            z=long(temp[0])*(10L**len(temp[1]))+long(temp[1])
            n=10L**len(temp[1])
            if temp[2]=='+':
                z*=10L**long(temp[3])
            if temp[2]=='-':
                n*=10L**long(temp[3])
            return Bruch(z,n)
        else:
            raise TypeError,"no compatiple type for operation"
    def _ggt(self,x,y):
        while y > 0:
            x,y=y,x%y
        return x
    def _kgv(self,x,y):
        return (x/self._ggt(x,y))*y

    def __add__(self,other):
        other = self._forceBruch(other)
        return Bruch(self.z * other.n + other.z * self.n, self.n * other.n)
    def __cmp__(self,other):
        try:
            other = self._forceBruch(other)
        except TypeError:
            return cmp(id(self),id(other))
        else:
            kgv=self._kgv(self.n,other.n)
            zself=self.z*(kgv/self.n)
            zother=other.z*(kgv/other.n)
            return cmp(zself,zother)
    def __div__(self,other):
        other = self._forceBruch(other)
        if other.z==0L:
            raise ZeroDivisionError, "integer division or modulo by zero"
        return Bruch(self.z * other.n, self.n * other.z)
    def __divmod__(self,other):
        other = self._forceBruch(other)
        return (self.__div__(other),self.__mod__(other))
    def __mod__(self,other):
        other = self._forceBruch(other)
        if other.z==0:
            raise ZeroDivisionError, "integer division or modulo by zero"
        ns=self._kgv(self.n,other.n)
        zself=self.z*(ns/self.n)
        zother=other.z*(ns/other.n)
        return Bruch(zself%zother,ns)
    def __mul__(self,other):
        other = self._forceBruch(other)
        if other.n==0L:
            raise ZeroDivisionError, "integer division or modulo by zero"
        return Bruch(self.z * other.z, self.n * other.n)
    def __pow__(self,other,z=None):
        if (other%1)==0:
            other = self._forceBruch(other)
            if other.z>=0:
                erg=Bruch(self.z**other.z,self.n**other.z)
            else:
                erg=Bruch(self.n**(-other.z),self.z**(-other.z))
        else:
            tempself=float(self)
            tempother=float(other)
            erg=self._forceBruch(tempself**tempother)
        if z==None:
            return erg
        else:
            return erg%z        
    def __radd__(self,other):
        return self.__add__(other)
    def __rdiv__(self,other):
        other = self._forceBruch(other)
        if self.z==0L:
            raise ZeroDivisionError, "integer division or modulo by zero"
        return Bruch(other.z * self.n, other.n * self.z)
    def __rdivmod__(self,other):
        return (self.__rdiv__(other),self.__rmod__(other))
    def __rmod__(self,other):
        other = self._forceBruch(other)
        if self.z==0:
            raise ZeroDivisionError, "integer division or modulo by zero"
        ns=self._kgv(self.n,other.n)
        zself=self.z*(ns/self.n)
        zother=other.z*(ns/other.n)
        return Bruch(zother%zself,ns)
    def __rmul__(self,other):
        return self.__mul__(other)
    def __rpow__(self,other,z=None):
        if z==None:
            return other**float(self)
        else:
            return (other**float(self))%z
    def __rsub__(self,other):
        other = self._forceBruch(other)
        return Bruch(other.z * self.n - self.z * other.n, other.n * self.n)
    def __sub__(self,other):
        other = self._forceBruch(other)
        return Bruch(self.z * other.n - other.z * self.n, self.n * other.n)

    def __abs__(self):
        return Bruch(abs(self.z),abs(self.n))
    def __float__(self):
        return float(self.z)/float(self.n)
    def __int__(self):
        return int(self.__long__())
    def __long__(self):
        if self.n==1:
            return long(self.z)
        else:
            return long(float(self.z)/float(self.n))
    def __neg__(self):
        return Bruch(-self.z,self.n)
    def __pos__(self):
        return Bruch(self.z,self.n)
    def __repr__(self):
        return "Bruch(%i,%i)"%(self.z,self.n)
    def __str__(self):
        return "(%i / %i)"%(self.z,self.n)
EDIT: kleine Verbesserungen in Sachen Geschwindigkeit, ein kleiner Bug in __pow__ behoben. Als Initialargumente sind nun auch Brüche selbst erlaubt.
Zuletzt geändert von Milan am Freitag 29. Oktober 2004, 21:34, insgesamt 7-mal geändert.
joerg
User
Beiträge: 188
Registriert: Samstag 17. August 2002, 17:48
Wohnort: Berlin
Kontaktdaten:

Hallo Milan,
Milan hat geschrieben:Ich hab nen paar Geschwindigkeitstest gemacht, aber so sehr ich auch an deinem Script rumgebastelt hab (habs auch noch mal ganz neu versucht), es war immer langsamer als meins.
Kein Widerspruch von mir, Deine Version sieht wirklich viel schöner aus. ;-)

Es war halt mein erster Versuch... und ich habe Abfragen drin, die verhindern sollen, daß Zähler und Nenner unnötig groß werden. Aber da Du sowieso noch kürzt, sind die ja irrelevant.

Jörg
Antworten