Frage

Hier sind die Ziele, die ich zu erreichen bin versucht:

  • Ich brauche 32-Bit-IEEE packen in 30 Bit schwimmt.
  • Ich möchte, dies zu tun, indem die Größe der Mantisse um 2 Bits verringert wird.
  • Der Betrieb selbst sollte so schnell wie möglich sein.
  • Ich bin mir bewusst, dass einige Genauigkeit verloren gehen, und das ist akzeptabel.
  • Es wäre von Vorteil, wenn dieser Vorgang nicht besondere ruinieren würde Fällen wie SNaN, QNaN, Unendlichkeiten, usw. Aber ich bin bereit, diese über Geschwindigkeit zu opfern.

Ich denke, diese Fragen besteht aus zwei Teilen:

1) Kann ich löschen einfach nur die am wenigsten signifikanten Bits der Mantisse? Ich habe dies versucht, und so weit es funktioniert, aber vielleicht für Probleme ich frage ... So etwas wie:

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;

2) Wenn es Fälle, in denen 1) fehlschlagen, was dann wäre der schnellste Weg, dies zu erreichen?

Vielen Dank im Voraus

War es hilfreich?

Lösung 4

Ich kann nicht eine der Antworten als die definitiven eine wählen, weil die meisten von ihnen gültige Informationen haben, aber nicht ganz das, was ich suchte. Also werde ich nur meine Schlussfolgerungen zusammenfassen.

Das Verfahren zur Umwandlung ich in meiner Frage ist Teil gepostet habe 1) ist eindeutig falsch von C ++ Standard, so dass andere Methoden Extrakt Float des Bits verwendet werden sollen.

Und am wichtigsten ... soweit ich aus der Lektüre der Antworten und anderen Quellen über IEEE754 Schwimmer verstehen, es ist in Ordnung, die am wenigsten signifikanten Bits von Mantisse fallen zu lassen. Es wird meist nur Präzision beeinflussen, mit einer Ausnahme: SNaN. Da SNaN durch Exponenten Satz auf 255 dargestellt wird, und Mantisse! = 0, kann es Situation, wo Mantisse- würde <= 3, und die letzten beiden Bits fallen würde SNaN zu +/- Unendlich konvertieren. Aber da SNaN nicht während Gleitkommaoperationen auf CPU, seinen sicheren unter kontrollierten Umgebung erzeugt wird.

Andere Tipps

Sie verletzen tatsächlich die strengen Aliasing-Regeln (siehe Abschnitt 3.10 des C ++ Standard) mit diesen reinterpret Würfen. Dies wird wahrscheinlich in Ihrem Gesicht explodiert, wenn Sie auf den Compiler-Optimierungen machen.

C ++ Standard Abschnitt 3.10 Ziffer 15 sagt:

  

Wenn ein Programm versucht, den gespeicherten Wert eines Objekts durch einen L-Wert von anderen zugreifen als einer der folgenden Typen ist das Verhalten nicht definiert

     
      
  • der dynamische Typ des Objekts,
  •   
  • eine cv-qualifizierte Version des dynamischen Typ des Objekts,
  •   
  • eine Art ähnlich den dynamischen Typ des Objekts,
  •   
  • ein Typ, der mit oder ohne Vorzeichen-Typ entsprechend dem dynamischen Typ des Objekts ist,
  •   
  • ein Typ, der die Unterzeichnung oder unsigned Typ entsprechend einem cv-qualifizierte Version des dynamischen Typ des Objekts,
  •   
  • ein Aggregat oder Union Typ, die eine der vorher erwähnten Typen unter den Mitgliedern enthält (einschließlich, rekursiv, ein Mitglied einer Unteraggregat oder enthaltenen Union)
  •   
  • ein Typ, der ein (möglicherweise cv-qualifiziert) Basisklassentyp des dynamischen Typs des Objekts ist,
  •   
  • a char oder unsigned char-Typ.
  •   

Insbesondere 3.10 / 15 erlauben uns nicht, über einen L-Wert vom Typ unsigned int einen Schwimmer Objekt zuzugreifen. Ich habe mich eigentlich von diesem gebissen. Das Programm, das ich schrieb aufgehört zu arbeiten, nachdem auf Optimierungen drehen. Offenbar GCC nicht einen L-Wert vom Typ float zu alias einem L-Wert vom Typ int erwarten, die von 3.10 / 15 eine fairen Annahme. Die Anweisungen werden vom Optimierer unter der schlurfte um As-if Regel 3.10 / 15 zu nutzen und es funktioniert nicht mehr.

Unter dem folgenden Annahmen

  • float entspricht wirklich zu einem 32-Bit-IEEE-Float,
  • sizeof (float) == sizeof (int)
  • unsigned int hat keine Füllbits oder Trap-Darstellungen

Sie sollten in der Lage sein, es so zu tun:

/// returns a 30 bit number
unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return r >> 2;
}

float unpack_float(unsigned int x) {
    x <<= 2;
    float r;
    std::memcpy(&r,&x,sizeof r);
    return r;
}

Diese leidet nicht unter der „3.10-Verletzung“ und ist in der Regel sehr schnell. Mindestens MEMCPY GCC behandelt als eine intrinsische Funktion. Für den Fall, müssen Sie nicht die Funktionen zur Arbeit mit NaNs, Unendlichkeiten oder Zahlen mit extrem hohem Betrag Sie sogar Genauigkeit durch Ersetzen „r >> 2“ mit „(r + 1) >> 2“ verbessern können:

unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return (r+1) >> 2;
}

Dies funktioniert auch, wenn es um die Exponenten aufgrund eines Mantissenüberlauf ändert, weil die IEEE-754-Codierung bildet aufeinanderfolgende Gleitkommawerte zu aufeinanderfolgenden ganzen Zahlen (ohne Berücksichtigung von +/- Null). Diese Zuordnung annähert tatsächlich einen Logarithmus ganz gut.

einfach blind den 2 LSBs des Schwimmers fallen kann für kleine Anzahl von ungewöhnlichem NaN Kodierungen fehlschlagen.

Ein NaN als Exponent codiert = 255, Mantisse! = 0, aber IEEE-754 sagt nichts über die mantiassa Werte verwendet werden soll. Wenn der Mantisse-Wert <= 3, können Sie eine NaN in eine Unendlichkeit drehen konnten!

Sie sollten es in einer Struktur einkapseln, so dass Sie nicht versehentlich mit regelmäßigen „unsigned int“ die Verwendung des markierten Schwimmer mischen:

#include <iostream>
using namespace std;

struct TypedFloat {
    private:
        union {
            unsigned int raw : 32;
            struct {
                unsigned int num  : 30;  
                unsigned int type : 2;  
            };
        };
    public:

        TypedFloat(unsigned int type=0) : num(0), type(type) {}

        operator float() const {
            unsigned int tmp = num << 2;
            return reinterpret_cast<float&>(tmp);
        }
        void operator=(float newnum) {
            num = reinterpret_cast<int&>(newnum) >> 2;
        }
        unsigned int getType() const {
            return type;
        }
        void setType(unsigned int type) {
            this->type = type;
        }
};

int main() { 
    const unsigned int TYPE_A = 1;
    TypedFloat a(TYPE_A);

    a = 3.4;
    cout << a + 5.4 << endl;
    float b = a;
    cout << a << endl;
    cout << b << endl;
    cout << a.getType() << endl;
    return 0;
}

Ich kann nicht garantieren, seine Portabilität though.

Wie viel Präzision benötigen Sie? Wenn 16-Bit-Float genug (ausreichend für einige Arten von Grafiken), dann ILM 16-Bit-Float ( „halb“), einen Teil der OpenEXR ist groß, gehorcht alle Arten von Regeln (http://www.openexr.com/ ), und Sie werden viel Platz übrig bleiben, nachdem Sie sie in eine Struktur zu packen.

Auf der anderen Seite, wenn Sie den ungefähren Bereich von Werten wissen, dass sie nehmen sind, sollten Sie Fixpunkt in Betracht ziehen. Sie sind nützlicher als die meisten Menschen bewusst ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top