2012-10-04 13 views
44

Qualcuno può spiegare in modo semplice i seguenti codici:C# funzione 'non sicuri' - * (float *) (e risultato) vs. (float) (risultato)

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: quanto sopra codice utilizza non sicuro funzione

per il codice di cui sopra, sto avendo difficoltà perché non capisco qual è la differenza tra il valore di ritorno confronta con il valore di ritorno di seguito:

return (float)(result); 

È necessario utilizzare la funzione non sicura se si restituisce *(float*)(&result)?

+0

dove hai trovato il campione? Non aveva alcuna spiegazione su cosa intendesse tornare? –

+0

è errato ... il risultato non è un puntatore ... non posso convertire il suo indirizzo in virgola mobile, immagino – Anirudha

+1

Trovo il codice di esempio usando ILSpy in mscorlib/System/BitConvert/ToSingle. Non è stata fornita alcuna spiegazione Devo capire qual è il flusso perché ho bisogno di convertirlo in PHP. – gchimuel

risposta

75

On. NET a float viene rappresentato utilizzando un numero mobile a precisione singola IEEE binary32 memorizzato utilizzando 32 bit. Apparentemente il codice costruisce questo numero assemblando i bit in un int e quindi lo proietta su un float utilizzando unsafe. Il cast è ciò che nei termini C++ è chiamato reinterpret_cast dove non viene eseguita alcuna conversione quando viene eseguito il cast: i bit vengono semplicemente reinterpretati come un nuovo tipo.

IEEE single precision floating number

Il numero assemblato è 4019999A in esadecimale o 01000000 00011001 10011001 10011010 in binario:

  • Il bit di segno è 0 (che è un numero positivo).
  • I bit di esponente sono 10000000 (o 128) con conseguente esponente 128 - 127 = 1 (la frazione viene moltiplicata per 2^1 = 2).
  • I bit di frazione sono 00110011001100110011010 che, se non altro, hanno quasi uno schema riconoscibile di zero e uno.

Il valore restituito ha gli stessi bit identici a 2.4 convertiti in virgola mobile e l'intera funzione può essere semplicemente sostituita dal valore letterale 2.4f.

Lo zero finale che tipo di "interrompe il pattern di bit" della frazione è forse in grado di far corrispondere il float a qualcosa che può essere scritto utilizzando un letterale in virgola mobile?


Quindi qual è la differenza tra un cast normale e questo strano "cast non sicuro"?

assumere il codice seguente:

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

Il primo cast prende il numero intero 1075419546 e lo converte in sua rappresentazione in virgola mobile, ad esempio 1075419546f. Ciò comporta il calcolo dei bit di segno, esponente e frazione richiesti per rappresentare il numero intero originale come un numero in virgola mobile. Questo è un calcolo non banale che deve essere fatto.

Il secondo cast è più sinistro (e può essere eseguito solo in un contesto non sicuro). Il &result prende l'indirizzo di result restituendo un puntatore alla posizione in cui è memorizzato il numero intero 1075419546.L'operatore di dereferencing del puntatore * può quindi essere utilizzato per recuperare il valore puntato dal puntatore. Tuttavia, utilizzando *&result verrà recuperato il numero intero memorizzato nella posizione, convertendo prima il puntatore a float* (un puntatore a float) un oggetto mobile recuperato dalla posizione di memoria con conseguente assegnazione del numero 2.4f mobile a unsafeCast. Quindi la descrizione di *(float*) &result è assegnami un puntatore a result e supponiamo che il puntatore sia puntatore a float e recuperi il valore puntato dal puntatore.

Contrariamente al primo cast, il secondo cast non richiede calcoli. Sposta semplicemente il 32 bit memorizzato in result in unsafeCast (che fortunatamente è anche a 32 bit).

In generale, un cast del genere può fallire in molti modi, ma usando unsafe stai dicendo al compilatore che sai cosa stai facendo.

+0

"Lo zero finale che tipo di" interrompe il pattern di bit "della frazione è forse lì per far sì che il float abbia una rappresentazione decimale esatta?" non è esatto per se - pensa come in decimale giriamo da 2/3 a 0.66667 non 0.66666 – Random832

+1

+! per usare la parola "sinister" – Nahum

+0

@ Random832: sei corretto che 2.4 non può essere rappresentato esattamente usando un numero a virgola mobile a 32 bit (non me ne ero reso conto inizialmente), ma esistono numeri che non sono frazioni infinite in entrambi i decimali e il sistema di numerazione binario. Per esempio. 11/4 = 2.75 decimale = 10.11 binario. –

2

Re: Che cosa sta facendo?

Sta prendendo il valore dei byte memorizzati int e invece interpreta questi byte come float (senza conversione).

Fortunatamente, floats e int sono lo stesso data size di 4 byte.

3

Questo sembra un tentativo di ottimizzazione. Invece di eseguire calcoli in virgola mobile si stanno eseguendo calcoli integer sulla rappresentazione Integer di un numero in virgola mobile.

Ricordare che i valori vengono memorizzati come valori binari come gli interi.

Al termine del calcolo si utilizzano puntatori e casting per convertire il numero intero in valore float.

Questo non equivale a trasmettere il valore a un valore float. Ciò trasformerà il valore int 1 in float 1.0. In questo caso, il valore int viene convertito nel numero in virgola mobile descritto dal valore binario memorizzato nell'int.

È abbastanza difficile da spiegare correttamente. Cercherò un esempio :-)

Edit: Guardate qui: http://en.wikipedia.org/wiki/Fast_inverse_square_root

Il codice è fondamentalmente facendo lo stesso come descritto in questo articolo.

18

se sto interpretando ciò che il metodo sta facendo correttamente, questo è un equivalente di sicurezza:

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

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

Come è stato già detto, si tratta di re-interpretazione del valore int come float.

+2

Conversione intelligente! Mi chiedo come la performance della versione sicura sia paragonata alla versione non sicura. –

+2

@RuneGrimstad Probabilmente non è così grande, considerando che sta copiando i dati invece di eseguire il casting su un puntatore di un tipo diverso. Tuttavia, è probabilmente il modo più semplice per farlo senza usare codice non sicuro. –

+0

concordato. Se può anche risultare che solo fare calcoli float direttamente sarebbe più veloce. Ancora una soluzione davvero bella! –

0

Come altri hanno già descritto, sta trattando i byte di un int come se fossero un galleggiante.

si potrebbe ottenere lo stesso risultato senza l'utilizzo di codice non sicuro come questo:

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

Ma allora non sarà molto veloce più e si potrebbe anche usare carri/doppie e le funzioni matematiche.

+2

La tua risposta è esattamente la stessa di quella di Bradley Smith. – Guillaume

2

Perché Sarge Borsch ha chiesto, ecco l'equivalente 'Unione':

[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

Fantastico! Inoltre, ho sentito parlare di questa cosa in un libro (CLR via C#), ma non ho capito che può essere usato in questo modo. –