2012-10-04 12 views
44

¿Puede alguien explicar de una manera sencilla los códigos siguientes:C# 'insegura' función - * (float *) (& resultado) vs (float) (resultado)

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: lo anterior código utiliza insegura función

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

return (float)(result); 

¿Es necesario utilizar la función no segura si devuelve *(float*)(&result)?

+0

Dónde encontraste la muestra? ¿No tenía ninguna explicación sobre lo que se supone que debe devolver? –

+0

está mal ..'result' no es un puntero ... no puedo convertir su dirección en un puntero flotante supongo – Anirudha

+1

El código de muestra se utiliza con el ILSpy en el mscorlib/System/BitConvert/ToSingle. No se dio ninguna explicación. Necesito entender cuál es el flujo porque necesito convertirlo a PHP. – gchimuel

Respuesta

75

En .NET a float se representa utilizando un número flotante de precisión simple IEEE binary32 almacenado utilizando 32 bits. Aparentemente el código construye este número ensamblando los bits en un int y luego lo lanza a un float usando unsafe. El elenco es lo que en términos de C++ se llama reinterpret_cast donde no se realiza ninguna conversión cuando se realiza el reparto; los bits simplemente se reinterpretan como un nuevo tipo.

IEEE single precision floating number

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 de exponente son 10000000 (o 128) que dan como resultado el exponente 128 - 127 = 1 (la fracción se multiplica por 2^1 = 2).
  • Los bits de fracción son 00110011001100110011010 que, como mínimo, casi tienen un patrón reconocible de ceros y unos.

volvió El flotador tiene los mismos bits exactas como 2,4 convertidos a punto flotante y toda la función simplemente se pueden sustituir por el literal 2.4f.

El cero final que tipo de "rompe el patrón de bits" de la fracción ¿existe quizás para hacer que el flotante coincida con algo que se puede escribir usando un literal de punto flotante?


Entonces, ¿cuál es la diferencia entre un reparto regular y esto rara "fundido insegura"?

asumir el siguiente código:

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

La primera fundido lleva el número entero 1075419546 y la convierte a su representación de punto flotante, por ejemplo, 1075419546f. Esto implica calcular los bits de signo, exponente y fracción necesarios para representar el número entero original como un número de coma flotante. Este es un cálculo no trivial que debe hacerse.

El segundo lanzamiento es más siniestro (y solo se puede realizar en un contexto inseguro). El &result toma la dirección de result devolviendo un puntero a la ubicación donde se almacena el número entero 1075419546.El operador de desreferenciación del puntero * se puede usar para recuperar el valor apuntado por el puntero. El uso de *&result recuperará el entero almacenado en la ubicación; sin embargo, primero colocando el puntero en float* (un puntero a float), se recupera un flotador de la ubicación de la memoria, lo que da como resultado 2.4f que está asignado al unsafeCast. Entonces la narrativa de *(float*) &result es dame un puntero a result y asumo que el puntero apunta a float y recupera el valor apuntado por el puntero.

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

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

+0

"¿El cero final que tipo de" rompe el patrón de bits "de la fracción es tal vez para hacer que el flotante tenga una representación decimal exacta?" no es exacto per se - ¡piense cómo en decimal redondeamos 2/3 a 0.66667 no 0.66666 – Random832

+1

+! para usar la palabra "siniestro" – Nahum

+0

@ Random832: Tiene razón de que 2.4 no se puede representar exactamente con un número de coma flotante de 32 bits (no me di cuenta al principio), pero existen números que no son fracciones infinitas en el decimal y el sistema de numeración binario. P.ej. 11/4 = 2,75 decimal = 10,11 binario. –

2

Re: ¿Qué está haciendo?

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

Afortunadamente, los flotantes y los ints tienen el mismo data size de 4 bytes.

3

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

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

Después de realizar el cálculo, está utilizando punteros y fundición para convertir el número entero en el valor flotante.

Esto no es lo mismo que emitir el valor de un flotador. Eso convertirá el valor int 1 en el flotante 1.0. En este caso, se convierte 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 correctamente. Buscaré un ejemplo. :-)

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

Su código está haciendo básicamente el mismo que se describe en este artículo.

18

si estoy interpretando lo que el método está haciendo correctamente, esto es un equivalente de seguridad:

public static float sample() {  
    int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); 

    byte[] data = BitConverter.GetBytes(result); 
    return BitConverter.ToSingle(data, 0); 
} 

Como se ha dicho ya, se re-interpretando el valor int como float.

+2

¡Conversión inteligente! Me pregunto cómo se compara el rendimiento de la versión segura con la versión insegura. –

+2

@RuneGrimstad Probablemente no sea tan bueno, teniendo en cuenta que está copiando los datos en lugar de simplemente enviarlos a un puntero de otro tipo. Aún así, es probablemente la forma más simple de hacerlo sin usar código inseguro. –

+0

de acuerdo. Si también resulta que hacer cálculos de flotación directamente sería más rápido. ¡Sigue siendo una buena solución! –

0

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

que podría obtener el mismo resultado sin necesidad de utilizar código no seguro como esto:

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

Pero entonces no va a ser muy rápido más y es posible también utilizar flotadores/dobles y las funciones matemáticas.

+2

Su respuesta es exactamente la misma que la de Bradley Smith. – Guillaume

2

Debido a Sarge Borsch pidió, aquí es equivalente a la '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; 
} 
+0

¡Impresionante! Además, he oído hablar de esto en un libro (CLR mediante C#), pero no me di cuenta de que se puede usar así. –