Rundungsfehler bei Python?

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
DODO2000
User
Beiträge: 4
Registriert: Donnerstag 28. Oktober 2010, 08:56

Wenn ich bei prozentuale Anteile berechne und diese dann addiere erhalte ich ein ergebnis um 100,15% voran kann das liegen?
vielen dank im voraus!
Benutzeravatar
lutz.horn
User
Beiträge: 205
Registriert: Dienstag 8. November 2005, 12:57
Wohnort: Pforzheim

Code?
https://www.xing.com/go/invite/18513630.6a91d4
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Falls es der allgemeine Rundungsfehler ist kannst du hier http://www.python-forum.de/viewtopic.php?t=4563 alternativen sehen.

Allerdings ist der Rundungfehler ziemlich groß, also wie sieht der Quellcode aus ?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
BlackJack

@DODO2000: Wenn es kein logischer Fehler im Programm ist, könnte es an Genauigkeitsproblemen bei der internen Darstellung von Fliesskommazahlen liegen. Das ist aber kein spezifisches Python-Problem -- das hättest Du in jeder Programmiersprach wenn Du Dort den selben Algorithmus implementierst und die selbe interne Darstellung von Fliesskommazahlen verwendet wird.
DODO2000
User
Beiträge: 4
Registriert: Donnerstag 28. Oktober 2010, 08:56

Code: Alles auswählen

with open("lm_origin-1.log","r") as input:
    with open("benutzerdaten.txt","w") as output:
        
            d={}         
            l=[]
            gesamt=0
            prozent=0
            zahl=0
            count=0
            pend =0
            for line in input.readlines():       
                if "Users of " in line:
                        prg=line.split()[2]
                        prg=prg[:-1]
                if ", start " in line:
                    username=line.split()[0]
                    if username in d:
                        if prg in d[username]:
                            d[username][prg]+=1
                            count+=1
                        else:
                            d[username][prg]=1
                            count+=1
                    else:
                        d[username]={}
                        d[username][prg]=1
                    
                    count+=1     
                                  
            for username in d:
                l.append(username)
                for prg in d[username]:
                    tmp=d[username][prg]
                    tmp=tmp/(count/100.0)*2
                    d[username][prg]=str(d[username][prg])+" ||| "+str(tmp)+"%" 
                    pend=pend+tmp
            
            count=0
            
         
                            
            l.sort()        
            for username in l:
                print(username+" "+str(d[username]))
                output.writelines(username+" "+str(d[username])+"\n")
                count+=1
            print(str(count)+" verschiedene Benutzer")
            output.writelines(str(count)+" verschiedene Benutzer")
            print "Gesamtprozente: " + str(pend) + "%"    
    output.close
input.close
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Na da hat sich aber einer Mühe gegeben, uns die wesentlichen Teile des Codes als lauffähiges Minimalbeispiel zu posten! ;-)

Die vermeindlichen close()-Aufrufe sind so übrigens falsch.

Code: Alles auswählen

# ist nur das Objekt der Methode close
input.close
# so callt man es...
input.close()
Davon abgesehen ist das beim Öffnen mit "with" ja gerade hinfällig und passiert automatisch. Das file Objekt ist an diesen Stellen bereits geschlossen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
DODO2000
User
Beiträge: 4
Registriert: Donnerstag 28. Oktober 2010, 08:56

das beantwortet jez aber nich meine frage oder? :K
BlackJack

@DODO2000: Ohne jetzt im Detail das ganze nachvollzogen zu haben, denke ich Du addierst da zu früh und zu viele ungenaue Zahlen auf. Statt der Prozente würde ich zur Kontrolle lieber die absoluten Zahlen aufaddieren und schauen ob da das gleiche heraus kommt.

Sonstige Anmerkungen:

Der Aufruf von `readlines()` ist überflüssig, weil Dateiobjekte selbt schon "iterable" sind und zwar liefern sie die Zeilen. Insbesondere bei Logdateien kann das einen Unterschied machen ob man die Zeilenweise liest und verarbeitet oder eine potenziell grosse Logdatei mit `readlines()` komplett in den Speicher liest.

Namen wie `l` oder `d` sind eindeutig zu kurz und nichtssagend.

Ein paar mehr Lehrzeichen um Operatoren und das Arbeiten mit Zeichenkettenformatierung statt zusammensetzen per ``+`` würde das Ganze IMHO lesbarer machen.

Du greifst zu oft und zu tief mittels Schlüsselzugriff durch die Datenstruktur die an `d` gebunden ist. Statt über die Schlüssel zu iterieren um dann in der Schleife über den Schlüssel an den Wert zu kommen, kann man die Schleife auch gleich über die (Schlüssel, Wert)-Paare laufen lassen.

Das Zählen in der Schleife über `input` kann man mit einem `collections.defaultdict` drastisch vereinfachen.

`count` schein mir überflüssig zu sein. Das ist nach der ersten Schleife redundant zu ``len(d)`` und nach der zweiten redundant zu ``len(l)``.

Das Aufbauen von Zeichenketten über ``+`` in einer Schleife ist sehr ineffizient. Der idiomatische Weg ist das erstellen einer Liste die man dann am Ende mittels `str.join()` zusammenfügt.

Das in einer Datenstruktur Zahlen durch Zeichenketten ersetzt werden, halte ich für schlechten Stil. Die Objekte sollten zumindest den selben Ducktyp und die selbe Bedeutung behalten. Sonst kann man schnell mal den Überblick verlieren zu welchem Zeitpunkt welche Daten an *der gleichen* "Stelle" stehen.

Ich würde wahrscheinlich hier auch schon auf OOP setzen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

DODO2000 hat geschrieben:das beantwortet jez aber nich meine frage oder? :K
Nein, ich habe Dir nur einen schnell ersichtlichen Fehler in Deinem Code gezeigt - quasi als gratis Bonus Dienstleistung ;-)

Mir ging es darum, dass Du Kommentar los einfach Deinen Quellcode hier hinhaust. Wir können das so doch gar nicht laufen lassen (wir haben keine Datendatei usw) und müssen somit rein analytisch vorgehen. Das wird aber durch den ganzen Boiler Plate-Code erschwert, der sich ums Parsing dreht usw.

Du hättest doch einfach mal ein kleines Mini-Beispiel schreiben können, bei dem Du einfach eine Liste von Werten über Deine Formel aufsummierst.

Der Fehler verbirgt sich eben wohl genau hier drin:

Code: Alles auswählen

tmp=tmp/(count/100.0)*2
Da Du Dir bei jedem Durchlauf einen Rundungsfehler einhandelst, kommt es eben in der Summe durchaus zu etwas "messbaren".

Also: Baue Dir doch mal ein Minimalbeispiel und zeige daran dokumentiert, was Du da ausrechnen willst. Ggf. kann man dann etwas finden, wie man den Rundungsfehler minimiert.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
DODO2000
User
Beiträge: 4
Registriert: Donnerstag 28. Oktober 2010, 08:56

also diese rechnung die ich da spaeter mit 2 multipliziert habe war nur ein versuch die ungenauigkeit zu umgehen aber : klappt nicht ich kann mir aber vorstellen das es an den string liegt weil diese ungenau umformen
BlackJack

@DODO2000: Hab's mal überarbeitet (ungetestet und natürlich immer noch mit Deinem Algorithmus):

Code: Alles auswählen

from collections import defaultdict
from functools import partial


def main():
    with open('lm_origin-1.log', 'r') as lines:
        username2program_and_count = defaultdict(partial(defaultdict, int))
        gesamt = 0
        for line in lines:
            if 'Users of ' in line:
                prg = line.split()[2][:-1]
            if ', start ' in line:
                username = line.split()[0]
                username2program_and_count[username][prg] += 1
                gesamt += 1

    pend = 0
    username2program_and_output = defaultdict(dict)
    for username, program2count in username2program_and_count.iteritems():
        for prg, count in program2count.iteritems():
            percent = count / (gesamt / 100.0) * 2
            pend += percent
            username2program_and_output[username][prg] = ('%d ||| %f%%'
                                                          % (count, percent))

    with open('benutzerdaten.txt', 'w') as output:
        for username, data in sorted(username2program_and_output.iteritems()):
            print username, data
            output.write('%s %s\n' % (username, data))
        user_count = len(username2program_and_output)
        print user_count, ' verschiedene Benutzer'
        output.write('%d verschiedene Benutzer\n' % user_count)
        print 'Gesamtprozente:', str(pend) + '%'


if __name__ == '__main__':
    main()
Da waren am Anfang ja noch unnötige Zuweisungen (`zahl` und `gesamt`). Das fällt einem eher auf wenn man nicht alles am Anfang definiert, sondern erst dann wenn man es benötigt.

Erstell doch mal eine Beispiel-Log-Datei bei der man das Problem nachvollziehen kann. Je kürzer (und immer noch mit Problem) desto besser.
Antworten