2015-12-03 11 views
5

In SSE è presente una funzione _mm_cvtepi32_ps(__m128i input) che accetta il vettore di input di numeri interi a 32 bit (int32_t) e li converte in float s.Come eseguire la conversione uint32/float con SSE?

Ora, voglio interpretare gli interi di input come non firmati. Ma non c'è nessuna funzione _mm_cvtepu32_ps e non sono riuscito a trovare un'implementazione di uno. Sai dove posso trovare una tale funzione o almeno dare un suggerimento all'implementazione? Per illustrare la differenza nei risultati:

unsigned int a = 2480160505; // 10010011 11010100 00111110 11111001 
float a1 = a; // 01001111 00010011 11010100 00111111; 
float a2 = (signed int)a; // 11001110 11011000 01010111 10000010 
+0

Se si utilizza 'int' a 32 bit, il valore positivo massimo è' 2147483647' in modo che il numero '2480160505' non possa essere rappresentato come' signed int'. –

+1

Immagino che l'OP significhi '2480160505U'? –

+0

Se i tuoi valori sono noti per essere in un intervallo limitato (penso '[0 .. 2^23]', poiché single-precision ha una mantissa a 23 bit, prova una versione a 32 bit di Mysticial's [64 bit non firmati da/per doppio] (http://stackoverflow.com/a/41148578/224132). Spiega il metodo abbastanza bene da essere in grado di trovare le costanti giuste per far funzionare il tratteggio del bit pattern IEEE-754. –

risposta

5

Questa funzionalità esiste in AVX-512, ma se non potete aspettare fino ad allora l'unica cosa che posso suggerire è quello di convertire i valori di input unsigned int in coppie di piccoli valori, convertirli e quindi aggiungerli di nuovo insieme, ad es

inline __m128 _mm_cvtepu32_ps(const __m128i v) 
{ 
    __m128i v2 = _mm_srli_epi32(v, 1);  // v2 = v/2 
    __m128i v1 = _mm_sub_epi32(v, v2);  // v1 = v - (v/2) 
    __m128 v2f = _mm_cvtepi32_ps(v2); 
    __m128 v1f = _mm_cvtepi32_ps(v1); 
    return _mm_add_ps(v2f, v1f); 
} 

UPDATE

Come notato da @wim in his answer, la soluzione sopra fallisce per un valore di ingresso UINT_MAX. Ecco una soluzione più robusta, ma un po 'meno efficace, che dovrebbe funzionare per l'intera gamma uint32_t ingresso:

inline __m128 _mm_cvtepu32_ps(const __m128i v) 
{ 
    __m128i v2 = _mm_srli_epi32(v, 1);     // v2 = v/2 
    __m128i v1 = _mm_and_si128(v, _mm_set1_epi32(1)); // v1 = v & 1 
    __m128 v2f = _mm_cvtepi32_ps(v2); 
    __m128 v1f = _mm_cvtepi32_ps(v1); 
    return _mm_add_ps(_mm_add_ps(v2f, v2f), v1f);  // return 2 * v2 + v1 
} 
+0

Grazie. A proposito, perché non usi val di riferimento negli argomenti di routine? –

+0

Vuoi dire passare 'v' a' _mm_cvtepu32_ps' come riferimento C++ o tramite un puntatore C o qualcosa, piuttosto che per valore? In pratica non fa alcuna differenza fintanto che la funzione è 'in linea'. –

+1

@KirillLykov: per i rari casi di funzioni che non vengono evidenziate, ma con argomenti vettoriali, il passaggio per valore è più efficiente. Invece di passare un puntatore in un registro intero, il vettore viene passato in un registro vettoriale. Quindi non deve essere rovesciato dal chiamante e ricaricato dal destinatario. (In realtà, nell'ABI di SysV, dovrebbe essere svuotato se il chiamante vuole mantenere il vecchio valore, perché in quell'ABI non ci sono reg di vettori preservati dalla chiamata, ma salverebbe la funzione chiamata dal caricarla, sebbene .) –

4

Credo che la risposta di Paolo è bello, ma non riesce per v = 4294967295U (= 2^32-1). In questo caso v2 = 2^31-1 e v1 = 2^31. _mm_cvtepi32_ps intrinseco converte da 2^31 a -2.14748365E9. v2 = 2^31-1 viene convertito in 2.14748365E9 e di conseguenza _mm_add_ps restituisce 0 (a causa dell'arrotondamento v1f e v2f sono l'esatto contrario dell'altro).

L'idea della soluzione seguente è copiare il bit più significativo di v in v_high. Gli altri bit di v sono copiati in v_low. v_high viene convertito in 0 o 2.14748365E9.

inline __m128 _mm_cvtepu32_v3_ps(const __m128i v) 
{ 
__m128i msk0=_mm_set1_epi32(0x7FFFFFFF); 
__m128i zero=_mm_xor_si128(msk0,msk0); 
__m128i cnst2_31=_mm_set1_epi32(0x4F000000); /* IEEE representation of float 2^31 */ 

__m128i v_high=_mm_andnot_si128(msk0,v); 
__m128i v_low=_mm_and_si128(msk0,v); 
__m128 v_lowf=_mm_cvtepi32_ps(v_low); 
__m128i msk1=_mm_cmpeq_epi32(v_high,zero); 
__m128 v_highf=_mm_castsi128_ps(_mm_andnot_si128(msk1,cnst2_31)); 
__m128 v_sum=_mm_add_ps(v_lowf,v_highf); 
return v_sum; 

} 



Aggiornamento

è stato possibile ridurre il numero di istruzioni:

inline __m128 _mm_cvtepu32_v4_ps(const __m128i v) 
{ 
__m128i msk0=_mm_set1_epi32(0x7FFFFFFF); 
__m128i cnst2_31=_mm_set1_epi32(0x4F000000); 

__m128i msk1=_mm_srai_epi32(v,31); 
__m128i v_low=_mm_and_si128(msk0,v); 
__m128 v_lowf=_mm_cvtepi32_ps(v_low); 
__m128 v_highf=_mm_castsi128_ps(_mm_and_si128(msk1,cnst2_31)); 
__m128 v_sum=_mm_add_ps(v_lowf,v_highf); 
return v_sum; 
} 

intrinseca _mm_srai_epi32 sposta il bit più significativo del v a destra, mentre si spostano i bit del segno, che risulta essere piuttosto utile h ere.

+1

Buona cattura - Non avevo considerato il caso limite di 'UINT_MAX' - Mi chiedo se c'è un modo più efficiente per farlo anche se? –

+1

@Paul R: Sembra che ci sia un modo leggermente più efficiente, vedi aggiornamento. Grazie per la tua osservazione. – wim

4

Con la soluzione di Paul R e con la mia soluzione precedente la differenza tra il punto mobile arrotondato e il numero intero originale è inferiore o uguale a 0,75 ULP (Unità nell'ultima posizione). In questi metodi può verificarsi un arrotondamento a due punti: in _mm_cvtepi32_ps e in _mm_add_ps. Questo porta a risultati che non sono il più precisi possibile per alcuni input.

Ad esempio, con il metodo di Paul R 0x2000003 = 33554435 viene convertito in 33554432.0, ma 33554436.0 esiste anche come float, che sarebbe stato meglio qui. La mia precedente soluzione soffre di imprecisioni simili. Tali risultati imprecisi possono anche verificarsi con il codice generato dal compilatore, see here.

Seguendo l'approccio di gcc (see Peter Cordes' answer to that other SO question), una conversione precisa all'interno di 0.5 ULP si ottiene:

inline __m128 _mm_cvtepu32_ps(const __m128i v) 
{ 
    __m128i msk_lo = _mm_set1_epi32(0xFFFF); 
    __m128 cnst65536f= _mm_set1_ps(65536.0f); 

    __m128i v_lo  = _mm_and_si128(v,msk_lo);   /* extract the 16 lowest significant bits of v         */ 
    __m128i v_hi  = _mm_srli_epi32(v,16);    /* 16 most significant bits of v             */ 
    __m128 v_lo_flt = _mm_cvtepi32_ps(v_lo);   /* No rounding                 */ 
    __m128 v_hi_flt = _mm_cvtepi32_ps(v_hi);   /* No rounding                 */ 
      v_hi_flt = _mm_mul_ps(cnst65536f,v_hi_flt); /* No rounding                 */ 
    return    _mm_add_ps(v_hi_flt,v_lo_flt); /* Rounding may occur here, mul and add may fuse to fma for haswell and newer */ 
}               /* _mm_add_ps is guaranteed to give results with an error of at most 0.5 ULP  */ 

noti che altri bit alto/basso partizioni bit sono possibili purché _mm_cvt_ps può convertire entrambi i pezzi ai flottanti senza arrotondamento. Ad esempio, una partizione con 20 bit alti e 12 bit bassi funzionerà altrettanto bene.

Problemi correlati