2010-12-01 12 views
20

Ich versuche, die Bits aus einem Float zu extrahieren, ohne undefiniertes Verhalten aufzurufen. Hier ist mein erster Versuch:float bits und striktes Aliasing

unsigned foo(float x) 
{ 
    unsigned* u = (unsigned*)&x; 
    return *u; 
} 

Wie ich es verstehe, ist dies nicht wegen der strengen Aliasing Regeln garantiert arbeiten, nicht wahr? Funktioniert es, wenn ein Zwischenschritt mit einem Zeichenzeiger gemacht wird?

unsigned bar(float x) 
{ 
    char* c = (char*)&x; 
    unsigned* u = (unsigned*)c; 
    return *u; 
} 

Oder muss ich die einzelnen Bytes selbst extrahieren?

unsigned baz(float x) 
{ 
    unsigned char* c = (unsigned char*)&x; 
    return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24; 
} 

Natürlich hat dies den Nachteil auf endianness von je, aber ich konnte damit leben.

Der Union Hack ist definitiv undefiniert Verhalten, oder?

unsigned uni(float x) 
{ 
    union { float f; unsigned u; }; 
    f = x; 
    return u; 
} 

Nur der Vollständigkeit halber, hier ist eine Referenzversion von foo. Auch undefiniertes Verhalten, oder?

unsigned ref(float x) 
{ 
    return (unsigned&)x; 
} 

So ist es möglich, die Bits von einem Schwimmer zu extrahieren (vorausgesetzt, beide sind 32 Bit breit, natürlich)?


EDIT: Und hier ist die memcpy Version wie von Goz vorgeschlagen. Da viele Compiler noch nicht static_assert unterstützen, habe ich static_assert mit etwas Metaprogrammierung ersetzt:

template <bool, typename T> 
struct requirement; 

template <typename T> 
struct requirement<true, T> 
{ 
    typedef T type; 
}; 

unsigned bits(float x) 
{ 
    requirement<sizeof(unsigned)==sizeof(float), unsigned>::type u; 
    memcpy(&u, &x, sizeof u); 
    return u; 
} 
+0

ich mit dem ersten Ansatz ein Problem nicht wirklich sehen, hat - Sie nicht einmal haben zwei Zeiger, die auf dasselbe Objekt zeigen. Sie sollten in Ordnung sein, obwohl Sie eine Kompilierungszeit behaupten möchten, dass sizeof (float) == sizeof (unsigned). Ich sehe auch kein Problem mit dem Gewerkschafts-Hack (obwohl ich die Größe erneut überprüfen würde). Aber ich bin sicher, dass es einige obskure Regeln gibt, die mir nicht bewusst sind. Lasst uns zurücklehnen und darauf warten, dass die Leute mir beweisen, dass ich falsch liege! – EboMike

+1

@Ebomike: Die erste Methode stört die strengen Aliasing-Regeln. Haben Sie einen Lese dafür: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html – Goz

+0

Danke, wusste ich, mich jemand falsch erweisen würde :) – EboMike

Antwort

16

Über die einzige Möglichkeit, wirklich alle Probleme zu vermeiden, ist zu MEMCPY.

Da Sie einen festen Betrag speichern, wird der Compiler es optimieren.

Das sagte die Union-Methode ist sehr weit verbreitet.

+0

Ich würde so weit gehen zu sagen, dass ich tatsächlich einen Fehler bei irgendeinem Compiler einreichen würde, der die Union-Methode nicht unterstützt. Ja, es ist technisch nicht Teil des Standards, aber es ist so weit verbreitet in Embedded-Programmierung verwendet, dass ein Compiler, der es nicht unterstützt, nicht sehr nützlich ist. – Crashworks

+0

@FredOverflow ... Tippfehler;) Korrigiert. – Goz

+0

@Crashworks: Sie würden gut gehen Berichterstattung ein Fehler ... es bedeutet nicht, der Compiler Schriftsteller einen Affen zu geben, hat aber;) Ihr Compiler könnte noch vollkommen kompatibel sein. – Goz

0

Wenn Sie wirklich wollen, über die Größe des Schwimmers Typ Agnostiker sein und nur die Rohbits zurückkehren, tun Sie etwas wie folgt aus:

void float_to_bytes(char *buffer, float f) { 
    union { 
     float x; 
     char b[sizeof(float)]; 
    }; 

    x = f; 
    memcpy(buffer, b, sizeof(float)); 
} 

es dann rufen Sie wie folgt:

float a = 12345.6789; 
char buffer[sizeof(float)]; 

float_to_bytes(buffer, a); 

Diese Technik erzeugt natürlich eine Ausgabe, die spezifisch für die Bytereihenfolge Ihrer Maschine ist.

6

Die Union Hack ist definitiv undefiniert Verhalten, oder?

Ja und nein. Gemäß dem Standard ist es definitiv undefiniertes Verhalten. Aber es ist solch ein häufig verwendeter Trick, dass GCC und MSVC und soweit ich weiß, jeder andere populäre Compiler explizit garantiert, dass es sicher ist und wie erwartet funktioniert.

+0

Von Interesse - welcher Teil davon ist undefiniertes Verhalten? (Abgesehen davon, dass Sie einen Float als Integer falsch interpretieren) – EboMike

+4

nur, dass es nicht erlaubt ist. Nur ein Mitglied einer Union ist jeweils "aktiv". Wenn Sie an ein Mitglied einer Struktur schreiben, dürfen Sie * nur * von demselben Mitglied lesen. Die Ergebnisse beim Lesen eines anderen Mitglieds sind nicht definiert. – jalf

+2

@EboMike "anders als" .. genau das ist UB. Es ist eine Alias-Verletzung, die von einem Mitglied gelesen wird, das nicht mit dem aktiven Mitglied der Union Aliasing-kompatibel ist. Folgendes ist zum Beispiel gut: 'union A {int a; unsigniertes Zeichen b; }; A x = {10}; return x.b; ', weil Sie auf einen' int' mit einem Lvalue vom Typ 'unsigned char' zugreifen dürfen. –

5

Die folgende nicht die Aliasing-Regel verstoßen, weil es keinen Gebrauch von lvalues ​​Zugriff auf verschiedene Arten überall

template<typename B, typename A> 
B noalias_cast(A a) { 
    union N { 
    A a; 
    B b; 
    N(A a):a(a) { } 
    }; 
    return N(a).b; 
} 

unsigned bar(float x) { 
    return noalias_cast<unsigned>(x); 
} 
+0

Dies beweist, dass der Standard gebrochen ist. Es ist lächerlich, dass temporary.member kein Wert ist. Ich nehme an, dass die Std-Typen durch die Begriffe "rvalue" (wie im Wert) und "rvalue" (ein temporäres) verwirrt wurden. lol – curiousguy

+1

@Johannes: Ist diese Argumentation immer noch wahr? Der Zugriff auf "b" ist der Zugriff auf ein nicht aktives Mitglied einer Union. – GManNickG