Rechnung in While-Schleife

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo zusammen

    Wenn ich folgen Code ausführe erwarte ich eigentlich, dass von 0.7 in 0.1er Schritten auf 0.0 herunterzählt.

    (Anm. zum Code: PWMLi und PWMRe haben je den Wert 0.7 und dies ist nur ein Teil des Codes. Gerne kann ich den ganzen teilen.)

    Leider macht die Schleife aber nicht was ich will. Zu beginn zählt es schön bei jedem Durchgang 0.1 herunter, dann aber plötzlich stimmt es nicht mehr. Danach erhalte ich die Fehlermeldung, da der Wert zwischen 0 und 1 sein muss (was ja auch logisch ist):

    Wieso zählt es da nicht schön in 0.1er Schritten herunter? Kann mir jemand helfen?

    PS: Es handelt sich um eine Steuerung eines Kettenfahrzeuges mit zwei Motoren. Die Motoren will ich jeweils über eine Rampe bremsen und beschleunigen.

    LG Bern

  • Das sind Rundungsfehler, weil du 0,3 nicht binär darstellen kannst.

    Am besten du zählst gleich von 7 bis 0 mit Integern.

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

  • Ach so danke vielmals Gnom

    Aber wie mache ich dann das, wenn der Wert nicht 1 überschreiten darf? Muss ich mit einer separaten Variable (Integer) rechnen, diese dann durch 10 teilen, damit ich sie für die PWM Steuerung nutzen kann? Oder gibt es eine elegantere Lösung?

  • Ja, würde ich genau so machen.
    Das Problem hast du auch bei Finanzsoftware. Die Rundungsfehler bei Cents würden bei Millionen Buchungen am Ende womöglich irgendwo Differenzen auftauchen lassen. Auch wenn das nur Pfennige sind, wird es unstimmig. Deshalb rechnen die intern mit Ganzzahlen als Cents, also 10000 (Cent) statt 100,00 (Euro). Bei der Ausgabe wird immer durch 100 geteilt.

    0,3 ist binär sowas wie 0,010011001100110011...

    Das sind 1/4 + 1/32 + 1/64 + 1/512 + 1/1024 + 1/8192 + 1/16384 + ...

    Am Ende gehen einem aber die Stellen aus und es wird nicht rund.

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

  • Es gibt Datenbanken und Programmierstparchen, die spezielle Datentypen für sowas haben. Intern ist das nichts anderes als eben diese Ganzzahlen. Die Rundungsgenauen zahlen aus der Datenbank nützen dir nur dann was, wenn du nicht hinterher mit denen weitere Rechnungen machst, die das Ganze wieder zum Kippen bringen. Mit dem Datenbank-Datentyp alleine ist es also oft nicht getan.

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

  • Im binären Zahlensystem können nicht alle rationalen Zahlen repräsentiert werden.

    Der Klassiker ist:

    Code
    >>> 0.2 + 0.1
    0.30000000000000004

    Damit man nicht so lange suchen muss: https://0.30000000000000004.com/


    Du hast mehrere Möglichkeiten:

    • aufsummieren und vor der Ausgabe der Summe auf eine Stelle hinter dem Komma runden
    • float vermeiden und stattdessen mit int arbeiten. Anstatt + 0.1 rechnest du dann +1. Vor der Anzeige durch 10 teilen und ggf. runden, sofern es dennoch zu Ungenauigkeiten bei der Ausgabe kommt.
    • mit Decimal arbeiten, was aber in der Ausführung langsamer ist
    Code: besser und das ohne Verenkungen
    for value in range(7, -1, -1):
        print(value / 10)
    Code: mit einer while Schleife
    total = 7
    print("Startwert:", total / 10)
    while total > 0:
        total -= 1
        print("Zwischenwert", total / 10)
    Python: geht auch, ist aber langsamer
    from decimal import Decimal
    
    total = Decimal("0.7")
    increment = Decimal("0.1")
    
    while total > 0:
        total -= increment
    
    print(total)
    print(float(total))

    Früher hat man das übrigens sofort gesehen, dass die Gleitkommazahlen ungenau sind, da es damals™ es noch keinen Algorithmus gab, der die angezeigten Gleitkommazahlen korrigiert hat. Die Korrektur der Ausgabe der Zahlen wird nicht angewandt, wenn damit gerechnet wird. Das dient alleinig dazu, den Menschen anzulügen. Deswegen hat man dieses unerwartete Verhalten bei manchen Gleitkommaberechnungen, aber nicht bei allen.

  • Hallo Bern,

    ein anderer zielführender Ansatz besteht darin, in der Schleife nicht jedes Mal 0,1 zu addieren, sondern die Schleifendurchläufe zu zählen und dann das Produkt Schleifendurchläufe * 0.1 zu addieren.

    Dein Ansatz führt zu einer Fehlerkette, die sich immer weiter vom erwarteten wahren Wert entfernt. Denn die Darstellung von 0.1 und das, was tatsächlich addiert wird, ist entweder immer geringstfügig kleiner oder geringstfügig größer als 0.100000000000000. Bei der Addition des Produktes gleichen sich die Fehler aus.

    Besser wird es, wenn Du mit Ganzzahlen arbeitest und erst vor der Addition aud Dezimalzahlen wandelst (wenn dies nicht anders geht).

    Bei der Addition einer Ganzzahl zu einer anderen Ganzzahl passieren keine Rundungsfehler (mal unterstellt, dass der Datentyp passend gewählt ist). Und bei der Division durch 10.0 hast Du nur einen einzigen Rundungsfehler. Die Ergebnisse sind mal geringstfügig kleiner oder geringstfügig größer als erwartet - aber nicht immer entweder größer oder kleiner als erwartet.

    Was Du aber generell - auch gedanklich - trennen musst: Den Wert einer Variablen für Berechnungen und der Wert zum Anzeigen eines Ergebnisses oder zum Speichern.

    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!