c# unsafe type-punning

Función "insegura" de C#-*(float*)(& resultado) vs.(float)(resultado)



unsafe type-punning (6)

¿Alguien puede explicar de manera simple los siguientes códigos:

public unsafe static float sample(){ int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); return *(float*)(&result); //don''t know what for... please explain }

Nota: el código anterior usa una función insegura

Para el código anterior, estoy teniendo dificultades porque no entiendo cuál es la diferencia entre su valor de retorno en comparación con el valor de retorno a continuación:

return (float)(result);

¿Es necesario usar una función no segura si está regresando *(float*)(&result) ?


Como Sarge Borsch preguntó, aquí está el equivalente de ''Unión'':

[StructLayout(LayoutKind.Explicit)] struct ByteFloatUnion { [FieldOffset(0)] internal byte byte0; [FieldOffset(1)] internal byte byte1; [FieldOffset(2)] internal byte byte2; [FieldOffset(3)] internal byte byte3; [FieldOffset(0)] internal float single; } public static float sample() { ByteFloatUnion result; result.single = 0f; result.byte0 = 154; result.byte1 = 153; result.byte2 = 25; result.byte3 = 64; return result.single; }


Como otros ya han descrito, se trata de los bytes de un int como si fueran un flotador.

Puede obtener el mismo resultado sin utilizar un código inseguro como este:

public static float sample() { int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); return BitConverter.ToSingle(BitConverter.GetBytes(result), 0); }

Pero entonces ya no será muy rápido y también podrías usar flotadores / dobles y las funciones matemáticas.


En .NET, un float se representa utilizando un número flotante de precisión simple IEEE binary32 almacenado con 32 bits. Aparentemente, el código construye este número al ensamblar los bits en un int y luego lo convierte en un float usando unsafe . La conversión es lo que en términos de C ++ se llama reinterpret_cast donde no se realiza ninguna conversión cuando se realiza la conversión; los bits se reinterpretan como un nuevo tipo.

El número ensamblado es 4019999A en hexadecimal o 01000000 00011001 10011001 10011010 en binario:

  • El bit de signo es 0 (es un número positivo).
  • Los bits del exponente son 10000000 (o 128) resultando en el exponente 128 - 127 = 1 (la fracción se multiplica por 2 ^ 1 = 2).
  • Los bits de fracción son 00110011001100110011010 que, si no más, casi tienen un patrón reconocible de ceros y unos.

El float devuelto tiene exactamente los mismos bits que 2.4 convertidos a punto flotante y la función completa puede simplemente reemplazarse por el literal 2.4f .

El cero final que el tipo de "rompe el patrón de bits" de la fracción ¿existe tal vez para hacer que el flotante coincida con algo que se puede escribir utilizando un literal de coma flotante?

Entonces, ¿cuál es la diferencia entre un reparto regular y este extraño "reparto inseguro"?

Supongamos el siguiente código:

int result = 0x4019999A // 1075419546 float normalCast = (float) result; float unsafeCast = *(float*) &result; // Only possible in an unsafe context

El primer lanzamiento toma el número entero 1075419546 y lo convierte a su representación de punto flotante, por ejemplo, 1075419546f . Esto implica el cálculo de los bits de signo, exponente y fracción requeridos para representar el entero original como un número de punto flotante. Este es un cálculo no trivial que se tiene que hacer.

El segundo lanzamiento es más siniestro (y solo se puede realizar en un contexto inseguro). El &result toma la dirección del result devolviendo un puntero a la ubicación donde se almacena el entero 1075419546 . El operador de desreferenciación del puntero * se puede utilizar para recuperar el valor apuntado por el puntero. El uso de *&result recuperará el número entero almacenado en la ubicación; sin embargo, al convertir el puntero en un float* (un puntero a un float ), se obtiene un flotante de la ubicación de la memoria, lo que hace que el flotador 2.4f se asigne a unsafeCast . Entonces, la narrativa de *(float*) &result es un puntero al result y asume que el puntero es un puntero a un float y recupera el valor apuntado por el puntero .

A diferencia del primer lanzamiento, el segundo lanzamiento no requiere ningún cálculo. Simplemente empuja los 32 bits almacenados en el result en unsafeCast (que afortunadamente también es de 32 bits).

En general, realizar un reparto como ese puede fallar de muchas maneras, pero al usar unsafe le está diciendo al compilador que sabe lo que está haciendo.


Esto parece un intento de optimización. En lugar de hacer cálculos de punto flotante, está haciendo cálculos de números enteros en la representación Integral de un número de punto flotante.

Recuerde, los flotadores se almacenan como valores binarios al igual que ints.

Una vez que se realiza el cálculo, está utilizando los punteros y la conversión para convertir el entero en el valor flotante.

Esto no es lo mismo que lanzar el valor a un flotador. Eso convertirá el valor int 1 en el float 1.0. En este caso, convierta el valor int en el número de punto flotante descrito por el valor binario almacenado en el int.

Es bastante difícil de explicar adecuadamente. Voy a buscar un ejemplo. :-)

Editar: Mira aquí: http://en.wikipedia.org/wiki/Fast_inverse_square_root

Su código básicamente hace lo mismo que se describe en este artículo.


Re: ¿Qué está haciendo?

Toma el valor de los bytes almacenados int y, en cambio, interpreta estos bytes como un flotante (sin conversión).

Afortunadamente, los flotadores y las entradas tienen el mismo tamaño de datos de 4 bytes.


Si estoy interpretando lo que el método está haciendo correctamente, este es un equivalente seguro:

public static float sample() { int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); byte[] data = BitConverter.GetBytes(result); return BitConverter.ToSingle(data, 0); }

Como ya se ha dicho, se está reinterpretando el valor int como un valor float .