2010-12-01 5 views
20

J'essaie d'extraire les bits d'un flottant sans invoquer de comportement indéfini. Voici ma première tentative:bits flottants et aliasing strict

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

Si je comprends bien, cela est pas garanti en raison des règles strictes d'aliasing, non? Est-ce que cela fonctionne si vous prenez une étape intermédiaire avec un pointeur de caractère?

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

Ou dois-je extraire moi-même les octets individuels?

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

Bien sûr, cela a l'inconvénient de dépendre de l'endianness, mais je pourrais vivre avec cela.

Le hack de l'union est certainement un comportement indéfini, non?

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

Juste pour être complet, voici une version de référence de foo. Aussi un comportement indéfini, non?

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

Alors, est-il possible d'extraire les bits d'un flotteur (si les deux sont très, bien sûr 32 bits)?


EDIT: Et voici la version memcpy proposée par Goz. Étant donné que de nombreux compilateurs ne supportent pas encore static_assert, je l'ai remplacé static_assert avec quelques métaprogrammation modèle:

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

je ne vois pas vraiment un problème avec la première approche - vous n'avez même pas deux pointeurs pointant vers le même objet. Cela devrait aller, même si vous voulez un temps de compilation affirmer que sizeof (float) == sizeof (unsigned). Je ne vois pas non plus de problème avec le hack de l'union (bien que je vérifierais encore la taille). Mais je suis sûr qu'il y a des règles obscures que je ne connais pas. Asseyons-nous et attendons que les gens me prouvent le contraire! – EboMike

+1

@Ebomike: La première méthode tombe sous le coup des règles strictes d'aliasing. Avoir une lecture de ceci: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html – Goz

+0

Merci, je savais que quelqu'un me prouverait faux :) – EboMike

Répondre

16

A propos de la seule façon d'éviter vraiment des problèmes est de memcpy. Parce que vous enregistrez une quantité fixe, le compilateur optimisera le nombre. Cela dit, la méthode d'union est très largement supportée.

+0

J'irais même jusqu'à dire que je déposerais un bogue sur n'importe quel compilateur qui ne supporterait pas la méthode union. Oui, techniquement, cela ne fait pas partie de la norme, mais il est si largement utilisé dans toute la programmation embarquée qu'un compilateur qui ne le supporte pas n'est pas très utile. – Crashworks

+0

@FredOverflow ... typo;) Fixe. – Goz

+0

@Crashworks: Vous feriez bien de rapporter un bogue ... cela ne signifie pas que l'auteur du compilateur doit donner un singe;) Leur compilateur pourrait toujours être parfaitement conforme. – Goz

0

Si vous voulez vraiment être agnostique sur la taille du flotteur et juste retourner les bits premières, faire quelque chose comme ceci:

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

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

Ensuite, appelez-le comme suit:

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

float_to_bytes(buffer, a); 

Cette technique produira, bien sûr, une sortie spécifique à l'ordre des octets de votre machine.

6

Le hack de l'union est définitivement un comportement non défini, non?

Oui et non. Selon la norme, c'est définitivement un comportement indéfini. Mais c'est un truc si commun que GCC et MSVC et autant que je sache, tous les autres compilateurs populaires, garantissent explicitement qu'il est sûr et fonctionnera comme prévu.

+0

Hors intérêt - quelle partie de ce comportement n'est pas défini? (sauf que vous interprétez mal un flottant comme un entier) – EboMike

+4

juste que ce n'est pas autorisé. Un seul membre d'un syndicat est "actif" à la fois. Si vous écrivez à un membre d'une structure, vous êtes seulement autorisé à lire à partir de ce même membre. Les résultats de la lecture de tout autre membre sont indéfinis. – jalf

+2

@EboMike "autre que" .. c'est exactement ce qui est UB. C'est une violation d'aliasing à lire d'un membre qui n'est pas alias compatible avec le membre actif de l'union. Ce qui suit est bien par exemple: 'union A {int a; char b non signé; } A x = {10}; return x.b; ', parce que vous êtes autorisé à accéder à un' int' par une lvalue de type 'unsigned char'. –

5

Ce qui suit ne viole pas la règle de l'aliasing, parce qu'il n'a pas l'utilisation de différents types lvalues ​​accès partout

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

Cela prouve que la norme est brisée. Il est ridicule que le membre temporaire ne soit pas une lvalue. Je suppose que les gars std ont été troublés par les termes "rvalue" (comme en valeur) et "rvalue" (un temporaire). lol – curiousguy

+1

@Johannes: Ce raisonnement est-il toujours vrai? Accéder 'b' permet d'accéder à un membre non-actif d'une union. – GManNickG