Domanda

Sto cercando di estrarre i bit da un galleggiante senza invocare un comportamento indefinito. Qui è il mio primo tentativo:

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

Se ho capito bene, questo non è garantito il funzionamento a causa di rigide regole di aliasing, giusto? Funziona se un fare un passo intermedio con un puntatore a carattere?

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

O devo estrarre i singoli byte solo?

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

Naturalmente questo ha lo svantaggio di seconda endianness, ma ho potuto vivere con questo.

L'hack unione è sicuramente un comportamento indefinito, giusto?

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

Solo per completezza, ecco un versione di riferimento di foo. Inoltre comportamento indefinito, giusto?

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

Quindi, è possibile estrarre i bit da un galleggiante ( assumendo entrambi sono 32 bit di larghezza , ovviamente)? ??


EDIT: E qui è la versione memcpy come proposto dal Goz. Dal momento che molti compilatori non supportano static_assert ancora, ho sostituito static_assert con alcuni template metaprogrammazione:

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;
}
È stato utile?

Soluzione

L'unico modo per evitare eventuali problemi veramente è quello di memcpy.

unsigned int FloatToInt( float f )
{
   static_assert( sizeof( float ) == sizeof( unsigned int ), "Sizes must match" );
   unsigned int ret;
   memcpy( &ret, &f, sizeof( float ) );
   return ret;
}

Poiché si sta memcpying un importo fisso il compilatore di ottimizzare fuori.

Detto questo il metodo di unione è molto ampiamente supportato.

Altri suggerimenti

  

L'hack unione è sicuramente un comportamento indefinito, giusto?

Sì e no. Secondo lo standard, è sicuramente un comportamento indefinito. Ma è un tale trucco comunemente usato che GCC e MSVC e per quanto ne so, ogni altro compilatore popolare, garantisce esplicitamente che è sicuro e funziona come previsto.

Di seguito non violare la regola aliasing, perché non ha alcun uso di lvalue di accesso a diversi tipi ovunque

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);
}

Se si vuole veramente essere agnostici circa la dimensione del tipo galleggiante e solo restituire i pezzi grezzi, fare qualcosa di simile:

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

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

Quindi chiamare in questo modo:

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

float_to_bytes(buffer, a);

Questa tecnica, naturalmente, prodotti potenza specifica di ordinamento dei byte della macchina.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top