c++ floating-point bits strict-aliasing type-punning

c++ - Flotante bits y estricto aliasing



floating-point strict-aliasing (4)

El hackeo sindical es definitivamente un comportamiento indefinido, ¿verdad?

Si y no. Según la norma, es definitivamente un comportamiento indefinido. Pero es un truco tan comúnmente utilizado que GCC y MSVC y, que yo sepa, todos los compiladores populares, explícitamente garantizan que es seguro y funcionarán como se espera.

Estoy intentando extraer los bits de un flotador sin invocar un comportamiento indefinido. Aqui esta mi primer intento:

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

Como lo entiendo, no se garantiza que esto funcione debido a las estrictas reglas de alias, ¿verdad? ¿Funciona si se toma un paso intermedio con un puntero de carácter?

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

¿O tengo que extraer los bytes individuales?

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

Por supuesto, esto tiene la desventaja de depender de la endianidad, pero podría vivir con eso.

El hackeo sindical es definitivamente un comportamiento indefinido, ¿verdad?

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

Sólo para completar, aquí hay una versión de referencia de foo . También el comportamiento indefinido, ¿verdad?

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

Entonces, ¿es posible extraer los bits de un flotador ( asumiendo que ambos tienen 32 bits de ancho , por supuesto)?

EDITAR: Y aquí está la versión memcpy propuesta por Goz. Dado que muchos compiladores todavía no admiten static_assert , he reemplazado static_assert con alguna metaprogramación de plantillas:

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


Acerca de la única manera de evitar realmente cualquier problema es 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; }

Debido a que usted está grabando una cantidad fija, el compilador la optimizará.

Dicho esto, el método de unión es MUY ampliamente apoyado.


Lo siguiente no viola la regla de alias, porque no utiliza lvalues ​​para acceder a diferentes tipos en cualquier lugar

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


Si realmente quieres ser agnóstico sobre el tamaño del tipo de coma flotante y simplemente devolver los bits en bruto, haz algo como esto:

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

Entonces llámalo así:

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

Esta técnica producirá, por supuesto, una salida específica para el ordenamiento de bytes de su máquina.