Absurder For-Schleifen Verlauf bei floats in 3.3.8.1 (Stable)?

  • 3.4 + 0.025 = 3.425
    oder
    34000 + 250 = 34250

    sollte man annehmen, AutoIt ist da bei mir allerdings anderer Meinung. Dort erhält man bei ersterer Rechnung nämlich 3.424999...9. Getestet habe ich das auf 3 verschiedenen PC nur mit der Stable und erhalte immer wieder periodische Fließkommazahlen wo eigentlich keine erscheinen dürften.

    Skript ist immer:

    [autoit]

    For $i = 2 to 3.5 Step 0.025
    ConsoleWrite("$i = " & $i & @LF)
    Next

    [/autoit]

    Alles geht gut, aber nur bis 3.4. Danach gibt es den gezeigten Fehler:

    Output

    Ist die Ursache nun tatsächlich ein Bug? (Mir ist klar, dass es auch hier Workarounds gibt, aber das darf ja nicht sein.)

    Einmal editiert, zuletzt von minx (1. August 2013 um 08:26)

    • Offizieller Beitrag

    Hallo,

    Auch interessant:

    Spoiler anzeigen
    [autoit]

    For $i = 1 to 1000 Step 0.025
    If $I > 999 Then ConsoleWrite("$i = " & $i & @LF)
    Next

    [/autoit]


    ergibt

    Spoiler anzeigen

    Sieht ganz klar nach nem Rundungsfehler von AutoIt aus. Sollte man vielleicht mal BugTracer erwähnen.

    Gruß,
    Spider

  • Warum das so ist wird z.B. in diesem Thread diskutiert. Das Problem dürfte in der binären internen Darstellung der Dezimalwerte liegen. Bei der Umwandlung von dezimal nach binär entstehen schon "Fehler" die sich dann bei der zweiten Umwandlung von binär nach dezimal zeigen. Sprich: Kein Bug, normales Verhalten.

  • Nö, das ist die interne Darstellung in der Hardware.
    Schau Dir mal diese Seite an und du wirst sehen, dass 3.4 intern schon mit einem "Rundungsfehelr" dargestellt wird.
    Aus 3,4 wird 3,4000000953674316

  • Für AutoIt vielleicht

    Es ist das Verhalten der Hardware und damit unabhängig von Programmiersprache.
    Wenn einfach mit internen festen Datentypen gerechnet wird kommt immer das heraus.

    Beispiel in C:

    Beispiel in C

    bringt folgende Ausgabe:

    Ausgabe

    Es ist sehr hilfreich sich etwas genauer mit der internen Repräsentation von Gleitkommazahlen zu beschäftigen.
    Das kann so manche böse Überraschung ersparen: >>Erläuterungen zu Gleitkommazahlen<<

  • Sehr gute und einfache Beschreibung. Danke.

  • Hi,
    das "Problem" ist so alt wie die binäre Darstellung bzw. die Verarbeitung der Gleitkommazahlen, also seit der Erfindung der Prozessoren / Rechner.
    Eine vom Computer verarbeitbare Binärzahl hat nun mal nur eine begrenzte Anzahl Bits zur Verfügung. Innerhalb dieser Anzahl Bits ist nur eine begrenzte Genauigkeit der Berechnung möglich!
    Die meisten "Kommazahlen" sind aber garnicht "genau" binär darstellbar!
    Daher gibt es bei den Prozessoren diverse (einstellbare) Möglichkeiten, die zwangsläufig fehlerbehaftete Berechnung über Rundung zu korrigieren.

    Das führt(e) schon dazu, dass identische Assemblerprogramme nach dem Assemblieren auf verschiedenen Prozessorplatformen zu unterschiedlichen Ergebnissen kamen, weil die Prozessoren nach dem Start das FPU-Control-Word unterschiedlich gesetzt hatten. Dieses Register bestimmt u.a. das Rundungsverhalten und die Genauigkeit der prozessorinternen Berechnung.

    Lustig wird es dann, wenn ein Programm das Controlword für eine beabsichtigt genaue Berechnung/Rundung ändert und danach nicht wieder zurücksetzt....
    Dann bekommt man unterschiedliche Ergebnisse einer Berechnung, nur weil im bspw. zwischenzeitlich zur Entspannung gezockten Spiels der Programmierer das Controlword geändert und nicht auf den ursprünglichen Wert zurückgesetzt hatte :S
    Das Problem hängt dann nicht einmal in der offensichtlich veränderten Genauigkeit, sondern bspw.in völlig veränderten Programmabläufen aufgrund von Vergleichen :wacko:
    Wenn man nach einer Berechnung in einem Programm das Ergebnis so auswertet

    Code
    If (float) a = (float) b then mache irgendetwas


    und immer ermittelt der Prozessor TRUE dann ist alles schick....
    Spielt man zwischendurch Tetris welches das FPU-Control-Word verändert (und nicht wieder zurücksetzt) und danach liefert unser Berechnungsprogramm für die Zeile

    Code
    If (float) a = (float) b then mache irgendetwas

    infolge anderer berechneter Werte plötzlich FALSE, dann hat man ein nicht unerhebliches Problem.....

    Von den "Problemen" bei Wertebereichen der FPU-Register von 80 Bit und dem Speicherbereich eines (single)floats von 32 Bit und eines (double) floats von 64 Bit garnicht zu reden....

    Letztendlich bleibt nur übrig, den "genauen" Wertebereich zu definieren bzw. darzustellen!

    [autoit]

    For $i = 2 to 3.5 Step 0.025
    ConsoleWrite( stringformat("$i = %f", $i) & @LF)
    Next

    [/autoit]

    Voila!

    Die Frage bleibt aber, ob der Prozessor richtig rechnet oder das Ergebnis "richtig" dargestellt wird 8o

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy (3. August 2013 um 08:49)

  • Letztendlich bleibt nur übrig, den "genauen" Wertebereich zu definieren bzw. darzustellen!

    [autoit]

    For $i = 2 to 3.5 Step 0.025
    ConsoleWrite( stringformat("$i = %f", $i) & @LF)
    Next

    [/autoit]


    Voila!
    Die Frage bleibt aber, ob der Prozessor richtig rechnet oder das Ergebnis "richtig" dargestellt wird


    Das Ergebnis wird hier lediglich gerundet. Die Standardanzeigegenauigkeit bei printf (also Stringformat) liegt bei 6 Nachkommastellen. Ein "%f" entspricht also implizit einem "%.6f".
    Gibt man z.b. "%.16f" hat man den Fehler wieder drin.
    Daher kann man statt die Stringausgabe auf 6 Stellen zu begrenzen gleich die Zahl auf 6 Nachkommastellen runden um den Fehler zu eliminieren.

  • Nehmen wir zum Beispiel die Zahl 2.85. Wenn wir diese in Binär umwandeln (auf 100 Nachkommastellen genau) so erhalten wir 10.110110011001100110011001100110011001100110011001101. Wenn wir diese Zahl wieder in Dezimal umwandeln erhalten wir (wer hätte es gedacht) 2.85. Wenn wir allerdings "nur" 20 Nachkommastellen benutzen erhalten wir 10.11011001100110011001. Wieder in Dezimal umgewandelt ergibt das 2.84999942779541.

    Wer also mehr Genauigkeit will, muss wohl oder übel auf einen anderen Datentyp umsteigen. (Oder mit Strings arbeiten, wie das Skript welches die in diesem Post verwendeten Werte errechnet hat.) Ob sich das, verglichen mit dem erhöhten Speicherverbrauch, überhaupt lohnt und wann man so viel Genauigkeit überhaupt braucht ist allerdings eine andere Frage.

    Ich weiß, ich habe nichts Neues hinzufügen können, wollte aber trotzdem ein kleines Beispiel einbringen.

  • AspirinJunkie,

    Zitat

    Das Ergebnis wird hier lediglich gerundet.

    Na, dass weiss ich doch!! :D
    Daher auch mein Statement:

    Zitat

    Die Frage bleibt aber, ob der Prozessor richtig rechnet oder das Ergebnis "richtig" dargestellt wird

    was ehrlich gesagt aber völlig unerheblich ist, denn das was "dort steht" ist per definitionem erstmal richtig (s. BILD-Zeitung).
    Es bleibt anderen überlassen, die Darstellung zu interpretieren bzw. "richtiges" von "falschem" zu unterscheiden!
    Würde das Consolewrite() die von dir genannte printf()-Funktion nutzen, wäre der "Fehler" im Startpost niemals aufgefallen, er hätte aber weiterhin bestanden!!! Oder doch nicht?

    Denn wie James in seinem Beispiel gezeigt hat, macht diese Diskussion nur dann Sinn, wenn die Anzahl der relevanten Nachkommastellen im Vorfeld festgelegt wird.
    Wenn ich mit

    [autoit]

    For $i = 2 to 3.5 Step 0.025

    [/autoit]

    definitiv 3 Nachkommastellen festlege, dann MUSS! in der nächsten Zeile

    [autoit]

    $i=round($i,3)

    [/autoit]

    oder eine Entsprechung auftauchen, alles andere führt zu verfälschten Ergebnissen!

    Die Frage stellt sich nun, warum der Compiler/Interpreter das nicht weiss ;)

  • Bei der hohen Entfernung von AutoIt zum Prozessor stellt sich mir die frage, warum AutoIt überhaupt intern mit "echten" floats rechnet.