Unklarheit bei einer for-Schleife



  • Hallo,

    obwohl ich schon einige Zeit C++ programmiere, bin ich heute zum ersten Mal auf folgenden Fehler gestossen:

    int main()
    {
    
      for(double i = -2; i <= 2; i += 0.2)
        {
          cout << i << endl;
        }
    
      return 0;
    }
    

    Der Code sollte eigenlich nur i von -2 bis +2 in Schritten von 0.2 inkrementieren lassen. Soweit klar allerdings bekomme ich folgenden Output:

    -2
    -1.8
    -1.6
    -1.4
    -1.2
    -1
    -0.8
    -0.6
    -0.4
    -0.2
    -2.77556e-16
    0.2
    0.4
    0.6
    0.8
    1
    1.2
    1.4
    1.6
    1.8
    2
    

    Warum steht bei 0 nicht 0? Wenn ich in 0.5er Schritten inkrementiere, passt alles einwandfrei und er zeigt 0.

    Sorry aber verstehe das gerade gar nicht 😃

    Ich verwende den g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609

    Kann mir da jemand helfen?

    Thx & Lg 🙂


  • Mod

    Dein Computer arbeitet (wie fast alle Computer) im Binärsystem. Im Binärsystem ist 0.2 nicht exakt mit endlich vielen Stellen darstellbar (ungefähr so, wie man 1/3 nicht exakt im Dezimalsystem ausschreiben kann). Wenn du 0.2 schreibst, rechnet der Computer in Wirklichkeit mit so etwas wie 0.20000000000000011102230246252, was der nächstbeste Wert für ihn ist. Aber 5 Mal diese Zahl ist eben nicht exakt 1, sondern ein bisschen mehr. Daher kommt deine for-Schleife nicht exakt bei 0 vorbei.

    0.5 ist hingegen exakt im Binärsystem darstellbar, da geht es gut.

    Jetzt fragst du vielleicht, wieso deine Zwischenwerte exakt zu stimmen scheinen. Das liegt an der Anzeigegenauigkeit. Die liegt standardmäßig bei 7 Stellen hinter der ersten Ziffer, die nicht 0 ist. Wenn er also bei -1.8 ist, dann ist er zwar in Wirklichkeit bei -1.79999999999999988897769753748, aber der wenn man das auf 7 Stellen rundet, dann kommt 1.8 heraus. Wenn er bei 0 ist, ist er hingegen bei 0.00000[ganz viele Nullen]000277556. Und wenn man das auf sieben relevante Stellen ausgibt, dann erhält man das, was du bekommst.

    Mögliche Abhilfen hängen davon ab, was du erreichen möchtest: Wenn es dir um die Anzeige geht, dann lässt sich an den Anzeigeeinstellungen schrauben. Wenn es dir darum geht, dass die Zahlen exakt sind, dann musst du anders rechnen, beispielsweise mit Brüchen.

    Beachte auch, dass Schleifen dieser Art eventuell nicht so oft laufen, wie man denkt. Hier war der interne Wert von 0.2 ein kleines bisschen größer als 0.2. Wenn du eine Schrittweite gewählt hättest, die intern etwas kleiner als erwartet wäre, dann wäre die Schleife einmal häufiger gelaufen.



  • Hi!

    danke für deine ausführliche Antwort, das hat mir jetzt geholfen! 🙂

    Lg
    Buzz



  • Das Verhalten rührt von Datentyp her. In anderen Sprachen wie z.B. C# kommt es zum gleichen Ergebnis. - Ich nehme mal an, dass hier die Datentyp-Genauigkeit eine Rolle spielt.

    Bei float sieht das Ergebnis noch mehr Kraut und Rüben aus.

    -2
    -1.8
    -1.6
    -1.4
    -1.2
    -0.99999998
    -0.79999998
    -0.59999998
    -0.39999999
    -0.19999999
    1.490116E-07
    0.20000002
    0.40000002
    0.60000001
    0.80000001
    1
    1.2
    1.4
    1.6
    1.8



  • Ergänzend zu dem was SeppJ schon gesagt hat, hier noch eine mögliche Lösung, falls du einfach nur sicherstellen willst, dass 0 auch tatsächlich exakt 0 ist:

    #include <iostream>
    
    int main() {
        for( int i = -10; i <= 10; ++i )
            std::cout << i * 0.2 << '\n';
    }
    

    EDIT: Diese Lösung hat ferner auch den Vorteil, dass die Schleife immer exakt 21 mal läuft, unabhängig davon ob der Offset ein kleines bisschen zu gross oder zu klein ist.



  • Ich weiß gar nicht mehr, wo/von wem ich es gehört habe, aber:

    Wenn du ein Problem hast (z.B. Schleife in 0.2er-Schitten durchlaufen) und versuchst es mit Floating-Point zu lösen, hast du auf einmal 2.000001 Probleme 😃


Anmelden zum Antworten