Qual è un modo corretto per digitare un float su un int e viceversa?

Il codice seguente esegue un’operazione di radice quadrata inversa rapida con alcuni hack. L’algoritmo è stato probabilmente sviluppato da Silicon Graphics nei primi anni ’90 ed è apparso anche in Quake 3. Ulteriori informazioni

Tuttavia ricevo il seguente avviso dal compilatore GCC C ++ : il puntatore con punteria di tipo dereferencing interromperà le regole di aliasing

Dovrei usare static_cast , reinterpret_cast o dynamic_cast invece in tali situazioni?

 float InverseSquareRoot(float x) { float xhalf = 0.5f*x; int32_t i = *(int32_t*)&x; i = 0x5f3759df - (i>>1); x = *(float*)&i; x = x*(1.5f - xhalf*x*x); return x; } 

Dimentica i calchi. Usa memcpy .

 float xhalf = 0.5f*x; uint32_t i; assert(sizeof(x) == sizeof(i)); std::memcpy(&i, &x, sizeof(i)); i = 0x5f375a86 - (i>>1); std::memcpy(&x, &i, sizeof(i)); x = x*(1.5f - xhalf*x*x); return x; 

Il codice originale tenta di inizializzare l’ int32_t accedendo per la prima volta all’object float tramite un puntatore int32_t , che è il punto in cui le regole vengono interrotte. Il cast in stile C equivale a reinterpret_cast , quindi cambiarlo in reinterpret_cast non farebbe molta differenza.

La differenza importante quando si usa memcpy è che i byte vengono copiati dal float int32_t , ma l’object float non viene mai acceduto attraverso un int32_t int32_t, perché memcpy prende i puntatori a vuoto e le sue parti interne sono “magiche” e non infrangono il regole di aliasing.

Ci sono alcune buone risposte qui che affrontano il problema del tipo di punire.

Voglio indirizzare la parte “radice quadrata inversa veloce”. Non usare questo “trucco” sui processori moderni. Ogni vettore mainstream ISA ha un’istruzione hardware dedicata per fornire una radice quadrata inversa rapida. Ognuno di loro è sia più veloce che più preciso di questo piccolo hack copiato.

Queste istruzioni sono tutte disponibili tramite intrinseche, quindi sono relativamente facili da usare. In SSE, si desidera utilizzare rsqrtss (intrinsico: _mm_rsqrt_ss( ) ); in NEON si desidera utilizzare vrsqrte (intrinsico: vrsqrte_f32( ) ); e in AltiVec si desidera utilizzare frsqrte . La maggior parte degli ISA GPU ha istruzioni simili. Queste stime possono essere raffinate usando la stessa iterazione di Newton, e NEON ha persino l’istruzione vrsqrts per fare parte del perfezionamento in una singola istruzione senza dover caricare le costanti.

Sto aggiungendo una risposta per non confutare la risposta accettata, ma per aumentarla. Credo che la risposta accettata sia corretta ed efficiente (e l’ho appena svalutato). Tuttavia volevo dimostrare un’altra tecnica altrettanto corretta ed efficiente:

 float InverseSquareRoot(float x) { union { float as_float; int32_t as_int; }; float xhalf = 0.5f*x; as_float = x; as_int = 0x5f3759df - (as_int>>1); as_float = as_float*(1.5f - xhalf*as_float*as_float); return as_float; } 

Usando clang ++ con l’ottimizzazione a -O3, ho compilato il codice di plasmacel, il codice di R. Martinho Fernandes e questo codice, e ho confrontato l’assemblaggio riga per riga. Tutti e tre erano identici.

Aggiornare

Non credo più che questa risposta sia corretta, a causa del feedback che ho ricevuto dal comitato. Ma voglio lasciarlo a scopo informativo. E sono intenzionalmente speranzoso che questa risposta possa essere corretta dal comitato (se sceglie di farlo). Cioè non c’è nulla riguardo all’hardware sottostante che rende questa risposta errata, è solo il giudizio di un comitato che lo rende tale o no.

L’unico cast che funzionerà qui è reinterpret_cast . (E anche allora, almeno un compilatore farà di tutto per assicurarsi che non funzioni.)

Ma cosa stai cercando di fare? Esiste sicuramente una soluzione migliore, che non prevede la punteggiatura di tipo. Ci sono pochissimi casi in cui il puning di tipo è appropriato, e sono tutti in codice molto, molto basso, cose come la serializzazione, o l’implementazione della libreria standard C (es. Funzioni come modf ). Altrimenti (e forse anche in serializzazione), funzioni come ldexp e modf probabilmente funzioneranno meglio e sicuramente saranno più leggibili.

Dai un’occhiata a questo per ulteriori informazioni su punire tipo e aliasing rigoroso.

L’unico cast sicuro di un tipo in un array è in un array di char . Se vuoi che un indirizzo dati sia commutabile in diversi tipi, dovrai usare un union

Il cast invoca un comportamento indefinito. Non importa quale forma di lancio usi, sarà comunque un comportamento indefinito. Non è definito a prescindere dal tipo di cast che utilizzi.

La maggior parte dei compilatori farà ciò che ti aspetti, ma a gcc piace essere cattivo e probabilmente supporrà che tu non abbia assegnato i puntatori nonostante tutte le indicazioni che hai fatto e riordinato l’operazione in modo da dare qualche risultato strano.

Lanciare un puntatore a un tipo e a un dereferenziamento incompatibili è un comportamento indefinito. L’unica eccezione è il casting su o da char, quindi l’unica soluzione è usare std::memcpy (come per la risposta di R. Martinho Fernandes). (Non sono sicuro di quanto sia definito usando i sindacati, ma ha comunque una migliore possibilità di lavorare).

Detto questo, non si dovrebbe usare il cast in stile C in C ++. In questo caso, static_cast non verrebbe compilato, né sarebbe dynamic_cast , costringendoti a utilizzare reinterpret_cast e reinterpret_cast è un forte suggerimento che potresti violare regole di aliasing severe.

Sulla base delle risposte qui ho realizzato una moderna funzione “pseudo-cast” per facilità di applicazione.

La versione C99 (mentre la maggior parte dei compilatori la supportano, in alcuni casi potrebbe essere un comportamento indefinito)

 template  inline T pseudo_cast(const U &x) { static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::value, "pseudo_cast can't handle types which are not trivially copyable"); union { U from; T to; } __x = {x}; return __x.to; } 

Versioni universali (in base alla risposta accettata)

Cast tipi con le stesse dimensioni:

 #include  template  inline T pseudo_cast(const U &x) { static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::value, "pseudo_cast can't handle types which are not trivially copyable"); static_assert(sizeof(T) == sizeof(U), "pseudo_cast can't handle types with different size"); T to; std::memcpy(&to, &x, sizeof(T)); return to; } 

Tipi di cast con qualsiasi dimensione:

 #include  template  inline T pseudo_cast(const U &x) { static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::value, "pseudo_cast can't handle types which are not trivially copyable"); T to = T(0); std::memcpy(&to, &x, (sizeof(T) < sizeof(U)) ? sizeof(T) : sizeof(U)); return to; } 

Usalo come:

 float f = 3.14f; uint32_t u = pseudo_cast(f);