float um kleinsten Schritt ändern.



  • Hallo,

    ich habe ein dummes Rundungs-Problem:

    float x=1234.5678;
    float y=1.2345678;
    

    Ich möchte einen bestehenden Float-Wert um den kleinsten Wert, der möglich ist,
    erhöhen bzw. vermindern. Die normale Addition ist unbrauchbar, da der Wert, der
    sich Addieren/Subtrahieren läßt, bei x und y unterschiedlich ist. Ich nehme an,
    daß ich da direkt die Mantisse mit Bit-Operationen behandeln muß. Wie geht das
    portabel?

    Danke, lg



  • round schrieb:

    Wie geht das portabel?

    Garnicht. Entweder Addition mit Epsilon oder tja...



  • round schrieb:

    Ich möchte einen bestehenden Float-Wert um den kleinsten Wert, der möglich ist,
    erhöhen bzw. vermindern.

    Wozu eigentlich?



  • der ieee standart sagt das die reihenfolge im binärformat linear ist, also in int umwandeln +1 und wieder zurück.



  • why schrieb:

    round schrieb:

    Ich möchte einen bestehenden Float-Wert um den kleinsten Wert, der möglich ist,
    erhöhen bzw. vermindern.

    Wozu eigentlich?

    Ein CAD-Tool. Die Koordinaten von Punkten liegen in hochgenauen rationalen
    Zahlen vor. Gesucht ist eine Repräsentation mit 32bit-Float. Die verändert
    die Geometrie und ist nicht immer zulässig. Eine Heuristik, die meistens
    funktionieren wird ist, daß man die nähesten Koordinaten aller Richtungen
    ausprobiert.



  • Deine rationalen Zahlen in float umwandeln wirst du wohl durch eine einfache Division, das gibt dir dann aber doch schon den jeweils nähesten float für jede Koordinate, oder!? Ansonsten 1 zur Mantisse addieren oder evtl. mit den Rounding Modes der FPU spielen.



  • Es gibt im C99-Standard eine Funktion nextafter, die das macht:

    #include <stdio.h>
    #include <math.h>
    
    int main() {
      double x = 1.234;
    
      printf("%.16lf, %.16lf\n", nextafter(x, 0.0), nextafter(x, 2.0));
    
      return 0;
    }
    
    $ gcc test.c && ./a.out 
    1.2339999999999998, 1.2340000000000002
    

    In C++98 ist diese leider nicht enthalten (weiß jemand, ob sie in C++11 vorgesehen ist?), aber eine Menge Compiler dürften sie inzwischen können. Mit MSVC bist du allerdings wahrscheinlich aufgeschmissen.

    Ansonsten könntest du über std::numeric_limits unter der Annahme, dass es sich um IEE754-Fließkommazahlen handelt, den Exponenten ausklamüsern und dein Epsilon selbst zusammensetzen, aber das ist alles andere als trivial. Behalte im Hinterkopf, dass IEE-754 subnormale Darstellungen erlaubt und das Epsilon je nach Richtung unterschiedlich groß sein kann.



  • Nachtrag: Eine Näherung (d.h. ohne die Bruchstellen) ausschließlich für 32-Bit-IEEE-754-floats:

    int intlog2(float f) {
      union {
        int i;
        float f;
      } v;
    
      int x;
      int c;
    
      v.f =   f;
      x   = v.i;
    
      c = x >> 23;
    
      if(c) {
        c -= 127;
      } else {
        int t;
        static const char LogTable256[256] =  {
    #define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
          -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
          LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
          LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
    #undef LT
        };
    
        if (t = x >> 16) {
          c = LogTable256[t] - 133;
        } else {
          c = (t = x >> 8) ? LogTable256[t] - 141 : LogTable256[x] - 149;
        }
      }
    
      return c;
    }
    
    float epsilon(float x) {
      return pow(2, intlog2(x) - 23);
    }
    

    intlog2 stammt hierher.

    Soweit ich das überblicke, verlässt sich der Code darauf, dass int 32 Bit breit ist (wie auch der float). Beim float kann man sich einigermaßen darauf verlassen - der Standard empfiehlt, float auf den IEEE-754-single zu mappen, und wenn IEE-754 nicht eingehalten wird, funktioniert das Ganze eh nicht - aber bei int wird das wahrscheinlich irgendwann nicht mehr der Fall sein. Ggf. solltest du also int32_t, __int32 o.ä. benutzen (bzw. auf boost/cstdint.hpp zurückgreifen).



  • dot schrieb:

    Deine rationalen Zahlen in float umwandeln wirst du wohl durch eine einfache Division, das gibt dir dann aber doch schon den jeweils nähesten float für jede Koordinate, oder!? Ansonsten 1 zur Mantisse addieren oder evtl. mit den Rounding Modes der FPU spielen.

    Ja, das gibt den nächsten Float. Der könnte aber die Geometrie in ungültiger
    Weise verändern, so daß ich von dort aus in alle Achsen-Richtungen up/down
    runden und so andere Kandidaten finden will.



  • Ich habe es per Schleife gelöst.

    void nextValsFloat(float x,float& xdn,float& xup)
    {
    	float addVal(1.0);
    	do
    	{
    		addVal/=2.0;
    	} while(x+addVal!=x);
    	addVal*=2.0;
    	cout<<"addVal to be used: "<<addVal<<endl;
    	xdn=x-addVal;
    	xup=x+addVal;
    
    }
    

    addVal darf maximal 1 sein, und eigentlich müßte man für xdn und xup den addVal
    separat ausrechnen. Aber für das gegebene Setting ist das okay.

    Danke für die Hinweise!



  • round schrieb:

    Der könnte aber die Geometrie in ungültiger Weise verändern, so daß ich von dort aus in alle Achsen-Richtungen up/down runden und so andere Kandidaten finden will.

    Dein eigentliches Problem hat also gar nichts mit

    float um kleinsten Schritt ändern

    zutun.



  • knivil schrieb:

    round schrieb:

    Der könnte aber die Geometrie in ungültiger Weise verändern, so daß ich von dort aus in alle Achsen-Richtungen up/down runden und so andere Kandidaten finden will.

    Dein eigentliches Problem hat also gar nichts mit

    float um kleinsten Schritt ändern

    zutun.

    Ein hochgenauer 3D-Punkt soll durch einen Punkt mit Floatingpoint-Koordinaten
    ersetzt werden. Da die automatische Rundung nicht zwangsläufig zu einem guten
    Punkt 'proxy' führt, sollen weitere Punkte innerhalb einer kleinen Sphere rund
    um proxy erzeugt werden. Daher die Notwendigkeit einer möglichst geringen
    Änderung von Float-Werten.


Log in to reply