float Ungenauigkeitsprpblem

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Hallo,
Ich schreibe gerade eine Klasse, die mathematische Funktionen analisiert. Methoden: zeros (für Nullstellen), extrema (für Extremas; Funktion, die von den Funktionen Maxima und Minima aufgerufen werden), derivative (Steigung an der x-Koordinate x) und natürlich __getitem__ für die y-Werte bei übergebenem x-Wert.
Alles läuft sehr gut, ist relativ schnell und findet es sehr präzise heraus, zumindest werden die Nullstellen alle berechnet.
Bei der extremafunktion ist das anders.
Ich nehme an, es ist die Ungenauigkeit von float, aber auf jeden Fall gibt es alleine fast 100 Ausgaben, an dem es mir an der x koordinate -4,85 eine Steigung von Null angibt :evil:
g.extrema(-5,5)
-4.8562478880000004, -4.8562478780000005, -4.8562478720000009, -4.8562478710000008, -4.8562478700000007, -4.8562478690000006, -4.8562478670000004, -4.8562478659749999, -4.8562478659650008, -4.8562478659300004, -4.8562478659200004, -4.8562478659000003, -4.8562478658900003, -4.8562478658800003, -4.8562478658600003, -4.8562478658500003, -4.8562478658400003, -4.8562478658300003, -4.8562478658200003, -4.8562478658100003, -4.8562478658000003, -4.8562478657900003, -4.8562478657800003, -4.8562478657700003, -4.8562478657600003, -4.8562478657500003, -4.8562478657400003, -4.8562478657300003, -4.8562478657200003, -4.8562478657100003, -4.8562478657000003, -4.8562478656900003, -4.8562478656800003, -4.8562478656700003, -4.8562478656600003, -4.8562478656500003, -4.8562478656400003, -4.8562478656300003, -4.8562478656200003, -4.8562478656100003, -4.8562478656000003, -4.8562478655900003, -4.8562478655800003, -4.8562478655700003, -4.8562478655600003, -4.8562478655500003, -4.8562478655400003, -4.8562478655300003, -4.8562478655200003, -4.8562478655100003, -4.8562478655000003, -4.8562478654900003, -4.8562478654700003, -4.8562478654600003, -4.8562478654500003, -4.8562478654400003, -4.8562478654300003, -4.8562478654100003, -4.8562478654000003, -4.8562478653900003, -4.8562478653800003, -4.8562478653700003, -4.8562478653600003, -4.8562478653500003, -4.8562478653300003, -4.8562478653100003, -4.8562478652900003, -4.8562478652700003, -4.8562478652500003, -4.8562478652300003, -4.8562478652050007, -4.8562478651949998, -4.8562478651650007, -4.8562478651549998, -4.8562478651250007, -4.8562478651100003, -4.8562478650900003, -4.8562478650700003, -4.8562478650500003, -4.8562478650300003, -4.8562478650100003, -4.8562478640000002, -4.856247862, -4.8562478610000008, -4.8562478590000007, -4.8562478580000006, -4.8562478560000004, -4.8562478550000003, -4.8562478539700003, -4.8562478539500002, -4.8562478539300002, -4.8562478539200002, -4.8562478539100002, -4.8562478539000002, -4.8562478538900002, -4.8562478538800002, -4.8562478538600002, -4.8562478538400002, -4.8562478537700002, -4.8562478537500002, -4.8562478537300002, -4.8562478537200002, -4.8562478537100002, -4.8562478537000002, -4.8562478536800002, -4.8562478536600002, -4.8562478536500002, -4.8562478536400002, -4.8562478535900002, -4.8562478535700002, -4.8562478535300002, -4.8562478535200002, -4.8562478535100002, -4.8562478535000002, -4.8562478534900002, -4.8562478534800002, -4.8562478534700002, -4.8562478534600002, -4.8562478534500002, -4.8562478534400002, -4.8562478534000002, -4.8562478533300002, -4.8562478533200002, -4.8562478533100002, -4.8562478532900002, -4.8562478532800002, -4.8562478532700002, -4.8562478532600002, -4.8562478532400002, -4.8562478532049997, -4.8562478531950006, -4.8562478531500002, -4.8562478531300002, -4.8562478531200002, -4.8562478531100002, -4.8562478530700002, -4.8562478530400002, -4.8562478530049997, -4.8562478529800002, -4.8562478529600002, -4.8562478529500002, -4.8562478529300002, -4.8562478529200002, -4.8562478529100002, -4.8562478528700002, -4.8562478528000002, -4.8562478527900002, -4.8562478527800002, -4.8562478527600001, -4.8562478527500001, -4.8562478527400001, -4.8562478527300001, -4.8562478527200001, -4.8562478527100001, -4.8562478526000001, -4.8562478525900001, -4.8562478525800001, -4.8562478525700001, -4.8562478525600001, -4.8562478525500001, -4.8562478525300001, -4.8562478525200001, -4.8562478525100001, -4.8562478524400001, -4.8562478524000001, -4.8562478523900001, -4.8562478523800001, -4.8562478523700001, -4.8562478523500001, -4.8562478523300001, -4.8562478523150006, -4.8562478523049997, -4.8562478522600001, -4.8562478522400001, -4.8562478522200001, -4.8562478521800001, -4.8562478521500001, -4.8562478521150005, -4.8562478521000001, -4.8562478520900001, -4.8562478520700001, -4.8562478520600001, -4.8562478520400001, -4.8562478520300001, -4.8562478520200001, -4.8562478500000008, -4.8562478490000007, -4.8562478440000003]
Meiner Meinung ist es sogar eher die Funktion, die die Steigung errechnet! Diese scheint einfach viel zu oft eine Steigung von null zu berechnen, da es nicht die vielen Nachkommastellen händeln kann.
Habe im Internet Das Modul decimal mit der Klasse decimal gefunden, aber das hat auch nicht geholfen.
Hat jemand eine andere Idee dazu?

Mein Quellcode:

Code: Alles auswählen

from math import*

class Function(object):
    def __init__(self, func):
        self.func = str(func)
        self._accuracy = 1e-10
        
    def __getitem__(self, x):
        try:
            res = eval(self.func)
        except:
            res = "ERROR"
        return res

    def zeros(self, a, b):
        intervals = 100
        step = float(b-a) / intervals
        zeros = []
        for intv in range(1, intervals):
            x1 = a + intv * step
            x2 = a + (intv+1) * step
            
            if self[x1] == 0:
                zeros.append(x1)
            elif self[x2] == 0:
                zeros.append(x2)
            elif (self[x1]<0 and self[x2]>0) or (self[x1]>0 and self[x2]<0):
                if abs(x1-x2) < self._accuracy:
                    zeros += [(x1+x2)/2]
                else:
                    zeros += self.zeros(x1,x2)

        return zeros

    def intersect(self, function):
        pass

    def derivative(self, x):
        delta_x = self._accuracy
        return (self[x + delta_x] - self[x]) / delta_x
    
    def extrema(self, a, b):
        intervals = 100
        step = float(b-a) / intervals
        extrema = []
        for intv in range(1, intervals):
            x1 = a + intv * step
            x2 = a + (intv+1) * step
            
            if self.derivative(x1) == 0 and len(extrema) and extrema[-1] != x1:
                extrema.append(x1)
            elif self.derivative(x2) == 0:
                extrema.append(x2)
            elif (self.derivative(x1) < 0 and self.derivative(x2) > 0) or (self.derivative(x1) > 0 and self.derivative(x2) < 0):
                if abs(x1-x2) < self._accuracy:
                    extrema += [(x1+x2)/2]
                else:
                    extrema += self.extrema(x1,x2)

        return extrema

    def maxima(self, a, b):
        maxima = []
        extrema = self.extrema(a,b)
        for i in range(len(extrema[:-1])):
            wert = self[(extrema[i] + extrema[i+1]) / 2]
            if wert > 0:
                maxima.append(extrema[i])
                
        return maxima
            
    def accuracy(self, value = None):
        if value != None:
            self._accuracy = value
        else:
            return self._accuracy

    def __repr__(self):
        return self.func

    __str__ = __repr__

if __name__=="__main__":
    f = Function("(x-3)**2-3")
    g = Function("x*sin(x**2)+1")
Viele Grüße Markus :D
BlackJack

@Markus12: Ein paar Anmerkungen, die aber alle nichts mit dem Problem zu tun haben, sorry. :-)

Statt `__getitem__` hätte ich bei einem Objekt, was eine Funktion repräsentiert eher `__call__` überschrieben. Und da dann auch nicht Ausnahmen durch die Zeichenkette 'ERROR' ersetzt. Wenn die jetzt auftaucht lässt einen das doch total im Dunkeln was die Ursache ist. Da wird Fehlersuche echt spassig.

Du könntest Dir mal die `compile()`-Funktion anschauen. Dann kannst Du den Übersetzungsschritt, der momentan noch von `eval()` bei jedem Aufruf übernommen wird, einmal in der `__init__()` erledigen. Dann würde dem Anwender beim erzeugen eines `Function`-Objekts schon ein `SyntaxError` um die Ohren fliegen, wenn die Funktion syntaktische Probleme hat, und nicht erst beim Versuch etwas damit auszurechnen.

`extrema()` und `zeros()` sind sich *so* ähnlich, dass man auf jeden Fall mal schauen könnte, was man da in eine Funktion herausziehen könnte.

Vielleicht doch zum Problem: In Zeile 50 der erste und der letzte Vergleich sind bei Fliesskommazahlen keine gute Idee. Ebenso in Zeile 52. Fliesskommazahlen auf *genaue* (Un)Gleichheit zu testen geht eben oft genug daneben.

`accuracy()` ist ein überflüssiger "Getter" und "Setter" und das auch noch in *einer* Funktion zusammengefasst. Mach aus `_accuracy` einfach `accuracy` und lass die Funktion weg.

`__repr__()` sollte nach Konvention entweder eine Zeichenkette liefern, die man mit `eval()` wieder in ein gleiches Objekt umwandeln kann, oder eine Beschreibung, die in grösser/kleiner Zeichen eeingefasst ist.

Die letzte Zuweisung in der Klasse ist überflüssig -- das ist sowieso schon das normale Verhalten von `__str__()`.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Markus12 hat geschrieben:die mathematische Funktionen analisiert.
:lol:
Bei der extremafunktion ist das anders.
Ich wuerde jetzt mal auf den ersten Blick sagen, dass das Verfahren, welches du dir ueberlegt hast, ungeeignet ist. Wenn man es genau wissen will, muesste man Fehlerabschaetzungen berechnen; aber erprobte numerische Verfahren kommen mit der Float-Genauigkeit hin oder umgehen sogar bekannte Probleme, die bei Float-Operationen auftreten koennen. (Gleitkommaarithmetik, insbes. Ausloeschung)

Mein Vorschlag: Lass die erste und zweite Ableitung als Parameter uebergeben und benutze dann das Newton-Verfahren, um Nullstellen und Extrema zu berechnen.

PS: Bitte poste keine solch langen Zeilen, man muss dann den ganzen Thread seitwaerts scrollen, und die Quotes in meinem Post muss man erstmal suchen...
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Markus12:

Könntest du deinen Code etwas kürzen? Nach 4-5 Elementen sollten Auslassungspunkte eigentlich genügen, um zu verstehen, dass es so in der Art weitergeht. Und es würde die Lesbarkeit des Threads ungemein verbessern. ;)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

snafu hat geschrieben:Könntest du deinen Code etwas kürzen? Nach 4-5 Elementen sollten Auslassungspunkte eigentlich genügen, um zu verstehen, dass es so in der Art weitergeht. Und es würde die Lesbarkeit des Threads ungemein verbessern. ;)
FTFY :)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Alternativ, kann man auch zwanglos scipy oder sympy nutzen. Na gut, das ist vielleicht nicht so lehrreich wie selbermachen, aber das kommt halt auf das Ziel an ...
Ein Beispiel mit scipy:

Code: Alles auswählen

from scipy import * # Sternchenimport nur in solchen Demos ;-)
g = lambda x: x*sin(x**2)+1
minima = [optimize.fmin_cg(g, x)[0] for x in range(-10, 11)]
HTH
Christian

PS den Wert für x sollte man ggf. etwas geschickter wählen, als über so eine einfache Liste, damit da was vernünftiges bei herum kommt. Grobes Raten mit Hilfe der Ableitung hilft da meist weiter.
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Ok, danke schonmal.

@CM:
Danke, aber ich will es ohne andere Module lösen :D
Dein Gedanke wegen meinem Verwenden einer Liste ist nicht an meinem Ende angekommen ^^

@Rebecca:
Ich hatte es schnell geschrieben und mein Browser hatte es rot unterstrichen, also habe ich einfach den netten Rechtsklick und die erste Version gewählt, woher soll ich wissen, dass die Rechtschreibhilfe so einen Mist produziert lach

Das Newton Verfahren kenne ich noch nicht. Das muss ich mir erstmal erlesen. Habe aber davon gehört.

@Blackjack:
Wusste noch nichts von einer __call__ funktion, hatte am Anfang auch eher nach runden Klammern gesucht zum Aufrufen, dachte das gibts nicht und habe dann enfach __getitem__ verwendet.

Die accuracy Funktion hatte ich eigentlich nur hereingemacht, weil sie mir praktisch erschien und ich das bei Tkinter mit der Funktion ´´title´´ gesehen habe. Kann ich also problemlos weglassen.

Viele Grüße Markus :)
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Markus12 hat geschrieben:@CM:
Danke, aber ich will es ohne andere Module lösen :D
Dein Gedanke wegen meinem Verwenden einer Liste ist nicht an meinem Ende angekommen ^^
Ok, das war vielleicht schludrig formuliert, ist aber ganz einfach: Das 'x' im Aufruf von .fmin_cg() stellt einen Startwert auf der Suche nach dem lokalen Minimum dar (Näheres in der scipy-Doku). Da ist es eben nicht so clever eine Liste von -10 bis 10 durchzugehen, denn das führt zu falschen Werte für die Minima. Besser ist es über die Ableitungen zu raten, wo Minima liegen können. Da es sich hier um Numerik und nicht Analytik handelt (das würde ein CAS wie sympy abdecken), ist die Ableitung eben eine schlechtere Näherung als die systematische Suche wie in den fmin_*-Varianten von scipy.optimize.

Aber das hilft Dir nur wenig weiter, wenn Du selber programmieren willst.

Viel Erfolg!
Christian
Markus12
User
Beiträge: 195
Registriert: Dienstag 6. März 2007, 19:32
Wohnort: Frankfurt am Main

Alles klar, danke für die Erklärung, habe ich nun verstanden :D
Antworten