Schrittweite bei floating point austüfteln ???



  • hallo, ich will in einer for-schleife von -3.14 bis +3.14 zählen. die schrittweite soll etwa 0.1 sein, aber so dass genau 3.14 erreicht wird. wie mache ich das am besten?

    ich könnte (2*pi)/60 teilen und das als increment nehmen (etwa 10 schritte zwischen n und n+1). wäre das okay so?
    kann es erst morgen ausprobieren, bin gerade nicht an einem computer wo ich was programmieren kann.



  • Du könntest von -31400 bis 31400 gehen und dabei bei jedem Schritt um 1256 erhöhen, sodass 31400 nach 50 Durchgängen erreicht wird (nur mal als Beispiel).


  • Mod

    +/- 3.14 ist problematisch, da es im Binärsystem ein periodischer Bruch ist. Daher wirst du sowieso nie auf exakt 3.14 kommen. Nehmen wir einfache Genauigkeit an, sind die beiden nächsten exakten Werte +-/ 3.1400001049041748046875. Das ist ein Abstand von 6.280000209808349609375 (was dann naturgemäß auch wieder exakt binär darstellbar ist). Das geteilt durch 0.1 ist schön nahe an einer anderen Zweierpotenz, 64. Also teilen wir durch 64, das Ergebnis sollte dann auch wieder exakt in einen float passen: 0.098125003278255462646484375.
    Ausgezeichnet.

    Probieren wir es aus:
    https://ideone.com/MlodFf
    Arggh! Die Idee war gut, aber scheitert daran, dass da Zwischenergebnisse rauskommen, die nicht mehr exakte floats sind. Aber was, wenn wir die Genauigkeit der Zwischenergebnisse erhöhen? Ein bisschen geschummelt würden die einen sagen, ich nenne es einen Kunstgriff:
    https://ideone.com/eILuSC
    Punktlandung. Nice.



  • Nie mit float zählen, wenn es irgendwie nachvollziehbares Ergebnis sein soll. Zähle mit einem int die Anzahl der Schritte die du haben willst (z.b. 6.3/0.1 = 63) und lerpe mit i/(max -1) zwischen den Werten.



  • @SeppJ Mit den gegebenen Anforderungen kann man schon exakt auf 3.14 kommen. Schliesslich will @Bushmaster ja nur eine Schrittweite von etwa 0.1 haben 😉

    Edit: Ja, habe grad verstanden, was du mit exakt gemeint hast. Sorry, hab nix gesagt. Gute Gedanken übrigens zu Fließkommazahlen im Binärsystem, die hätte ich mir nicht ansatzweise so gemacht - vor allem deine "Punktlandung" scheint genau der Wert zu sein, in den der Compiler ein 3.14f konvertiert 😉

    Mein Ansatz wäre, erstmal genau wie @SeppJ zu berechnen, wieviele Schritte meine for-Schleife benötigt, wenn die Schrittweite möglichst nah an 0.1 sein soll. Das sind n = round(abs(a - b) / 0.1), also 63.

    Nun mache ich die Schleife statt mit einem float-Zähler mit einem int-Zähler und rechne diesen Zähler in jedem Schleifendurchlauf in einen float-Wert (t = i / (n - 1)) um, der von 0 bis 1 läuft. Mit diesem mache ich dann eine lineare Interpolation zwischen dem Start- und Zielwert: (1.0f - t) * a + t * b

    Im letzten Schleifendurchlauf ist t = 62 / 62 = 1, also auch als Fließkommazahl exakt 1.0f, wodurch der brechnete Wert ebenfalls exakt 3.14f wird (nun gut, zumindest so "exakt" wie sich 3.14f darstellen lässt, @SeppJ erwähnte ja, dass das periodisch ist).

    Dass t in jedem Schleifendurchlauf aus dem Integer-Zähler berechnet wird, hat weiterhin den Vorteil, dass sich Rundungsfehler nicht aufsummieren und bei längeren Schleifen zu groß werden (Punktlandung wird schwerer):

    #include <cstdlib>
    #include <cmath>
    #include <iostream>
     
    constexpr float a = -3.14f;
    constexpr float b = 3.14f;
    constexpr float s = 0.1f;
    constexpr int n = std::round(std::abs(a - b) / s);
     
    auto main() -> int
    {
        std::cout.precision(15); 
    
        for (int i = 0; i < n; ++i)
        {
            float t = static_cast<float>(i) / static_cast<float>(n - 1);
            std::cout << i << ": " << (1.0f - t) * a + t * b << "\n";
        }
    }
    

    https://ideone.com/Sdek18

    P.S.: @SeppJ

    while (value != end)
    

    Spassig für so ein Experiment, aber "vorbildlich" würde ich das nur bedingt nennen (kids! don't try this at home!) 😉


  • Mod

    Es ging doch gerade darum, dass ich zeigen wollte, wie man so eine Punktlandung macht.



  • @SeppJ sagte in Schrittweite bei floating point austüfteln ???:

    +/- 3.14 ist problematisch, da es im Binärsystem ein periodischer Bruch ist. Daher wirst du sowieso nie auf exakt 3.14 kommen. Nehmen wir einfache Genauigkeit an, sind die beiden nächsten exakten Werte +-/ 3.1400001049041748046875. Das ist ein Abstand von 6.280000209808349609375 (was dann naturgemäß auch wieder exakt binär darstellbar ist). Das geteilt durch 0.1 ist schön nahe an einer anderen Zweierpotenz, 64. Also teilen wir durch 64, das Ergebnis sollte dann auch wieder exakt in einen float passen: 0.098125003278255462646484375.
    Ausgezeichnet.

    Probieren wir es aus:
    https://ideone.com/MlodFf
    Arggh! Die Idee war gut, aber scheitert daran, dass da Zwischenergebnisse rauskommen, die nicht mehr exakte floats sind. Aber was, wenn wir die Genauigkeit der Zwischenergebnisse erhöhen? Ein bisschen geschummelt würden die einen sagen, ich nenne es einen Kunstgriff:
    https://ideone.com/eILuSC
    Punktlandung. Nice.

    danke, das hat geholfen.



  • @TGGC sagte in Schrittweite bei floating point austüfteln ???:

    Nie mit float zählen, wenn es irgendwie nachvollziehbares Ergebnis sein soll. Zähle mit einem int die Anzahl der Schritte die du haben willst (z.b. 6.3/0.1 = 63) und lerpe mit i/(max -1) zwischen den Werten.

    stimmt.
    was auch noch ginge ist mit integer von -314 bis +314 zählen, mit schrittweite von 10, dann nach float casten und durch 100.0 teilen.
    das ist mir kurz danach eingfallen nachdem ich die frage stellte. immer erst ne minute nachdenken bevor man fragt. aber so hattet ihr auch euren spaß. 🙂
    und ich habe mich als noob geoutet.

    @yahendrik sagte in Schrittweite bei floating point austüfteln ???:

    Du könntest von -31400 bis 31400 gehen und dabei bei jedem Schritt um 1256 erhöhen, sodass 31400 nach 50 Durchgängen erreicht wird (nur mal als Beispiel).

    ja, sowas ist mir auch eingefallen. dass man floats nicht zum zählen nehmen soll, habe ich auch schon mal gehört aber vergessen.



  • @Bushmaster sagte in Schrittweite bei floating point austüfteln ???:

    @SeppJ sagte in Schrittweite bei floating point austüfteln ???:

    +/- 3.14 ist problematisch, da es im Binärsystem ein periodischer Bruch ist. Daher wirst du sowieso nie auf exakt 3.14 kommen. Nehmen wir einfache Genauigkeit an, sind die beiden nächsten exakten Werte +-/ 3.1400001049041748046875. Das ist ein Abstand von 6.280000209808349609375 (was dann naturgemäß auch wieder exakt binär darstellbar ist). Das geteilt durch 0.1 ist schön nahe an einer anderen Zweierpotenz, 64. Also teilen wir durch 64, das Ergebnis sollte dann auch wieder exakt in einen float passen: 0.098125003278255462646484375.
    Ausgezeichnet.

    Probieren wir es aus:
    https://ideone.com/MlodFf
    Arggh! Die Idee war gut, aber scheitert daran, dass da Zwischenergebnisse rauskommen, die nicht mehr exakte floats sind. Aber was, wenn wir die Genauigkeit der Zwischenergebnisse erhöhen? Ein bisschen geschummelt würden die einen sagen, ich nenne es einen Kunstgriff:
    https://ideone.com/eILuSC
    Punktlandung. Nice.

    danke, das hat geholfen.

    Nicht wirklich. Es erklärt nicht, warum die Zwischenergebnisse wieder keine exakten floats sind und in welchem Fall sie dann vlt. auch keine exakten doubles mehr sein würden. z.b. ist auch 1 ein exakter float und double, genau wie 2^n. Bis zu welchem n funktioniert aber ein for a = 1 to 2^n korrekt? Es gibt auch gar keine eindeutige Antwort darauf im C/C++ Universum.


  • Mod

    @TGGC sagte in Schrittweite bei floating point austüfteln ???:

    Nicht wirklich. Es erklärt nicht, warum die Zwischenergebnisse wieder keine exakten floats sind und in welchem Fall sie dann vlt. auch keine exakten doubles mehr sein würden. z.b. ist auch 1 ein exakter float und double, genau wie 2^n. Bis zu welchem n funktioniert aber ein for a = 1 to 2^n korrekt? Es gibt auch gar keine eindeutige Antwort darauf im C/C++ Universum.

    Also ich bin ca. 80% sicher, dass die von mir vorgestellte float-Methode immer funktionieren wird, wenn man die Zwischenergebnisse mit doppelter Genauigkeit speichert 🙂



  • @SeppJ Für die konkreten Werte? Und was ist funktionieren? Hält? Hält mit bestimmer Anzahl Iterationen? Alle Zwischenergbenisse identisch? Weil mit dem richtigen Endwert funktioniert ja auch die erste Variante, nur müsste man den eben erst in einer Schleife berechnen damit es "immer" funktioniert. Dann kann man in dieser Schleife auch schon die Arbeit machen.


  • Mod

    Ich meinte die Vorgehensweise:

    1. Zwei exakt darstellbare Zahlen nehmen
    2. Differenz dieser beiden durch eine Zweierpotenz teilen
    3. Iteratives Addieren der Differenz auf den kleineren Wert
    4. Exaktes Ankommen beim größeren Wert.

    Aber: Jetzt, wo ich diese Vorgehensweise aufschreibe, fallen mir lauter Sonderfälle ein, wo das Verfahren scheitern wird. Beispielsweise wenn in Schritt 2 die Zweierpotenz zu groß ist, dann kommt in Schritt 3 Unsinn heraus. Insofern: Jetzt bin ich 100% sicher, dass das Verfahren doch nicht allgemein anwendbar ist. Aber es war natürlich umgekehrt auch kein Zufall, dass mein obiges Vorgehen für nicht zu extreme Werte zum Erfolg geführt hat.



  • Das es geht ist schon eher der Sonderfall. Bei alles float oder alles double, geht es fast nie. Wenn start, ende, schrittweite floats sind und die Zählvariable double, geht es dann, wenn die Exponenten der Zahlen nicht zu weit auseinanderliegen im Vergleich zu den Nullen am Ende in der Mantisse ( jeder float endet als double mit 30(?) Nullen. Das ist aber auch nur eine Faustregel. Im Prinzip wird beim Iterieren die Mantisse geschiftet während die Exponenten aufeinander zulaufen. Werden dabei hinten Daten rausgeschiftet für ein Zwischenergebnis, kommt man nicht beim korrekten Wert an.



  • @TGGC sagte in Schrittweite bei floating point austüfteln ???:

    Nicht wirklich.

    doch schon. es hat mir gezeigt dass meine idee mit floats zu zählen eine totale schnapsidee war. 🙂


  • Mod

    @TGGC sagte in Schrittweite bei floating point austüfteln ???:

    Das es geht ist schon eher der Sonderfall. Bei alles float oder alles double, geht es fast nie. Wenn start, ende, schrittweite floats sind und die Zählvariable double, geht es dann, wenn die Exponenten der Zahlen nicht zu weit auseinanderliegen im Vergleich zu den Nullen am Ende in der Mantisse ( jeder float endet als double mit 30(?) Nullen. Das ist aber auch nur eine Faustregel. Im Prinzip wird beim Iterieren die Mantisse geschiftet während die Exponenten aufeinander zulaufen. Werden dabei hinten Daten rausgeschiftet für ein Zwischenergebnis, kommt man nicht beim korrekten Wert an.

    Der Fall hier, wo alles ungefähr die gleiche Größenordnung hat, mag ja technisch gesehen der Sonderfall sein, wenn man zufällige Werte aus dem gesamten Wertebereich einer Fließkommazahl heran zieht, aber für "normale" Beispiele, wie sie sich ein Mensch ausdenken würde (z.B. dieses), geht es.



  • Nie verkehrt, auch mal weniger ausgetretene Pfade zu erkunden. Der Brot-und-Butter-Integer-Lerp ist hier zwar besser, dafür wird man aber auch seltener Namensgeber eines innovativen neuen Algorithmus, wenn man immer nur solche Standardlösungen rezitiert - auch wenn das in so abgegrasten Feldern eher selten vorkommt 😉

    Dennoch besser, das etwas deutlicher als "experimentell" zu markieren, wenn auch Neulinge mitlesen (vor allem diese Abbruchbedingung) 😉



  • @Bushmaster sagte in Schrittweite bei floating point austüfteln ???:

    was auch noch ginge ist mit integer von -314 bis +314 zählen, mit schrittweite von 10, dann nach float casten und durch 100.0 teilen.
    das ist mir kurz danach eingfallen nachdem ich die frage stellte. immer erst ne minute nachdenken bevor man fragt. aber so hattet ihr auch euren spaß. 🙂
    und ich habe mich als noob geoutet.

    Wie soll das gehen?

    -314
    -304
    ...
    -  4
    +  6
    ...
    +306
    +316
    

    Punktlandung ist das keine.



  • Was du machen kannst:
    Zähl i von 0 bis 39564 in Schritten von 628. Dein Wert bei jedem Durchlauf ist dann x = (i - 19782) / 6300.0.

    Das sind dann genau 63 Schritte, wobei jeder Schritt 628/6300=0,099682539682539... ist.

    Ansonsten sollte auch diese wesentlich einfachere Variante genau genug für die allermeisten Anwendungen sein:

    #include <iostream>
     
    int main() {
        double const pi = 3.14; // oder auch gern genauer
        double const step = 2.0 * pi / 60.0;
        double const start = -pi;
        for (int i = 0; i <= 60; i++) {
            double const x = start + step * i;
            std::cout << x << "\n";
        }
    }
    

    https://ideone.com/jLQc2Z



  • @hustbaer sagte in Schrittweite bei floating point austüfteln ???:

    @Bushmaster sagte in Schrittweite bei floating point austüfteln ???:

    was auch noch ginge ist mit integer von -314 bis +314 zählen, mit schrittweite von 10, dann nach float casten und durch 100.0 teilen.
    das ist mir kurz danach eingfallen nachdem ich die frage stellte. immer erst ne minute nachdenken bevor man fragt. aber so hattet ihr auch euren spaß. 🙂
    und ich habe mich als noob geoutet.

    Wie soll das gehen?

    -314
    -304
    ...
    -  4
    +  6
    ...
    +306
    +316
    

    Punktlandung ist das keine.

    mit der angepassten schrittweite. 2*314, d.h 628 hat 4 als teiler, also nimmt man 4 als schrittweite. das macht 157 werte.
    dann landet man wieder auf'm punkt.
    sind das zu viele dann schmeißt man jeden zweiten raus. hauptsache der erste und letzte beiben erhalten.
    es geht um das plotten von kurven auf einem kleinen display. diese kurven haben alle eine wellenlänge von 2 pi.



  • @hustbaer sagte in Schrittweite bei floating point austüfteln ???:

    double const pi = 3.14; // oder auch gern genauer
    double const step = 2.0 * pi / 60.0;
    double const start = -pi;
    for (int i = 0; i <= 60; i++) {
    double const x = start + step * i;
    std::cout << x << "\n";
    }

    das gefällt mir sehr.


Log in to reply