Domanda

Ho bisogno di un cross-platform libreria / algoritmo che permette di convertire tra i 32-bit e numeri in virgola mobile a 16 bit. Non ho bisogno di eseguire la matematica con i numeri a 16 bit; Ho solo bisogno di ridurre le dimensioni dei 32-bit galleggia in modo che possano essere inviati attraverso la rete. Sto lavorando in C ++.

ho capito quanta precisione sarei perdendo, ma va bene per la mia applicazione.

Il formato IEEE a 16 bit sarebbe grande.

È stato utile?

Soluzione

std::frexp estrae il significante e l'esponente di carri normali o doppie - - allora avete bisogno di decidere cosa fare con gli esponenti che sono troppo grandi per entrare in un galleggiante mezza di precisione (saturare ...?), regolare di conseguenza, e mettere insieme il numero di semi-precisione. Questo articolo ha C codice sorgente per mostrare come eseguire la conversione.

Altri suggerimenti

completa conversione da singola precisione alla metà di precisione. Si tratta di una copia diretta da mia versione SSE, quindi è ramo-less. Si avvale del fatto che in GCC (-true == ~ 0), può essere vero per VisualStudio troppo ma, non ho una copia.

    class Float16Compressor
    {
        union Bits
        {
            float f;
            int32_t si;
            uint32_t ui;
        };

        static int const shift = 13;
        static int const shiftSign = 16;

        static int32_t const infN = 0x7F800000; // flt32 infinity
        static int32_t const maxN = 0x477FE000; // max flt16 normal as a flt32
        static int32_t const minN = 0x38800000; // min flt16 normal as a flt32
        static int32_t const signN = 0x80000000; // flt32 sign bit

        static int32_t const infC = infN >> shift;
        static int32_t const nanN = (infC + 1) << shift; // minimum flt16 nan as a flt32
        static int32_t const maxC = maxN >> shift;
        static int32_t const minC = minN >> shift;
        static int32_t const signC = signN >> shiftSign; // flt16 sign bit

        static int32_t const mulN = 0x52000000; // (1 << 23) / minN
        static int32_t const mulC = 0x33800000; // minN / (1 << (23 - shift))

        static int32_t const subC = 0x003FF; // max flt32 subnormal down shifted
        static int32_t const norC = 0x00400; // min flt32 normal down shifted

        static int32_t const maxD = infC - maxC - 1;
        static int32_t const minD = minC - subC - 1;

    public:

        static uint16_t compress(float value)
        {
            Bits v, s;
            v.f = value;
            uint32_t sign = v.si & signN;
            v.si ^= sign;
            sign >>= shiftSign; // logical shift
            s.si = mulN;
            s.si = s.f * v.f; // correct subnormals
            v.si ^= (s.si ^ v.si) & -(minN > v.si);
            v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
            v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
            v.ui >>= shift; // logical shift
            v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
            v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
            return v.ui | sign;
        }

        static float decompress(uint16_t value)
        {
            Bits v;
            v.ui = value;
            int32_t sign = v.si & signC;
            v.si ^= sign;
            sign <<= shiftSign;
            v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
            v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
            Bits s;
            s.si = mulC;
            s.f *= v.si;
            int32_t mask = -(norC > v.si);
            v.si <<= shift;
            v.si ^= (s.si ^ v.si) & mask;
            v.si |= sign;
            return v.f;
        }
    };

Ecco, questo è un sacco di prendere in, ma, gestisce tutti i valori subnormali, entrambi infiniti, tranquilla nans, segnalazione NaNs, e lo zero negativo. Naturalmente il pieno supporto IEEE non è sempre necessario. Così la compressione carri generici:

    class FloatCompressor
    {
        union Bits
        {
            float f;
            int32_t si;
            uint32_t ui;
        };

        bool hasNegatives;
        bool noLoss;
        int32_t _maxF;
        int32_t _minF;
        int32_t _epsF;
        int32_t _maxC;
        int32_t _zeroC;
        int32_t _pDelta;
        int32_t _nDelta;
        int _shift;

        static int32_t const signF = 0x80000000;
        static int32_t const absF = ~signF;

    public:

        FloatCompressor(float min, float epsilon, float max, int precision)
        {
            // legal values
            // min <= 0 < epsilon < max
            // 0 <= precision <= 23
            _shift = 23 - precision;
            Bits v;
            v.f = min;
            _minF = v.si;
            v.f = epsilon;
            _epsF = v.si;
            v.f = max;
            _maxF = v.si;
            hasNegatives = _minF < 0;
            noLoss = _shift == 0;
            int32_t pepsU, nepsU;
            if(noLoss) {
                nepsU = _epsF;
                pepsU = _epsF ^ signF;
                _maxC = _maxF ^ signF;
                _zeroC = signF;
            } else {
                nepsU = uint32_t(_epsF ^ signF) >> _shift;
                pepsU = uint32_t(_epsF) >> _shift;
                _maxC = uint32_t(_maxF) >> _shift;
                _zeroC = 0;
            }
            _pDelta = pepsU - _zeroC - 1;
            _nDelta = nepsU - _maxC - 1;
        }

        float clamp(float value)
        {
            Bits v;
            v.f = value;
            int32_t max = _maxF;
            if(hasNegatives)
                max ^= (_minF ^ _maxF) & -(0 > v.si);
            v.si ^= (max ^ v.si) & -(v.si > max);
            v.si &= -(_epsF <= (v.si & absF));
            return v.f;
        }

        uint32_t compress(float value)
        {
            Bits v;
            v.f = clamp(value);
            if(noLoss)
                v.si ^= signF;
            else
                v.ui >>= _shift;
            if(hasNegatives)
                v.si ^= ((v.si - _nDelta) ^ v.si) & -(v.si > _maxC);
            v.si ^= ((v.si - _pDelta) ^ v.si) & -(v.si > _zeroC);
            if(noLoss)
                v.si ^= signF;
            return v.ui;
        }

        float decompress(uint32_t value)
        {
            Bits v;
            v.ui = value;
            if(noLoss)
                v.si ^= signF;
            v.si ^= ((v.si + _pDelta) ^ v.si) & -(v.si > _zeroC);
            if(hasNegatives)
                v.si ^= ((v.si + _nDelta) ^ v.si) & -(v.si > _maxC);
            if(noLoss)
                v.si ^= signF;
            else
                v.si <<= _shift;
            return v.f;
        }

    };

In questo modo tutti i valori nella gamma accettato, nessun supporto per Nans, infiniti o pari a zero negativo. Epsilon è il più piccolo valore consentito nella gamma. Precisione è quanti bit di precisione per mantenere dal galleggiante. Mentre ci sono un sacco di rami sopra, sono tutti statici e verranno memorizzati nella cache dal predittore ramo nella CPU.

Naturalmente se i valori non richiedono risoluzione logaritmica prossimi allo zero. Poi li linearizzazione in un formato punto fisso è molto più veloce, come si è già accennato.

Io uso la FloatCompressor (versione SSE) nella libreria grafica per ridurre le dimensioni dei valori di colore galleggiante lineare nella memoria. carri compressi hanno il vantaggio di creare piccoli tavoli look-up per le funzioni di tempo, come la correzione gamma o trascendentali. Comprimendo valori sRGB lineari riduce ad un massimo di 12 bit o un valore massimo di 3011, che è grande per una dimensione di look-up table per da / sRGB.

Dato vostre esigenze (-1000, 1000), forse sarebbe meglio usare una rappresentazione in virgola fissa.

//change to 20000 to SHORT_MAX if you don't mind whole numbers
//being turned into fractional ones
const int compact_range = 20000;

short compactFloat(double input) {
    return round(input * compact_range / 1000);
}
double expandToFloat(short input) {
    return ((double)input) * 1000 / compact_range;
}

Questo vi darà la precisione a 0,05. Se si modifica 20000 per SHORT_MAX si otterrà un po 'più precisione, ma alcuni numeri interi finiranno come decimali sull'altra estremità.

La metà di galleggiare:
float f = ((h&0x8000)<<16) | (((h&0x7c00)+0x1C000)<<13) | ((h&0x03FF)<<13);
Float alla metà:
uint32_t x = *((uint32_t*)&f);
uint16_t h = ((x>>16)&0x8000)|((((x&0x7f800000)-0x38000000)>>13)&0x7c00)|((x>>13)&0x03ff);

Se si invia un flusso di informazioni attraverso, probabilmente si potrebbe fare meglio di questo, soprattutto se tutto è in un range coerente, come l'applicazione sembra avere.

Invia un piccolo colpo di testa, che proprio è costituito da un float32 minimo e massimo, quindi è possibile inviare attraverso le informazioni come un valore di interpolazione a 16 bit tra i due. Come dici tu, inoltre, che la precisione non è molto più di un problema, si potrebbe anche inviare 8bits alla volta.

Il valore sarebbe qualcosa di simile, in fase di ricostruzione:

float t = _t / numeric_limits<unsigned short>::max();  // With casting, naturally ;)
float val = h.min + t * (h.max - h.min);

La speranza che aiuta.

-Tom

Questa domanda è già un po 'vecchio, ma per ragioni di completezza, si potrebbe anche dare un'occhiata a questo documento per la conversione metà a galleggiante e galleggiante a metà.

Usano un approccio branchless tavolo-driven con relativamente piccole tabelle di. E 'completamente IEEE-conforme e addirittura supera IEEE-conformant routine di conversione di rami di Phernost in termini di prestazioni (almeno sulla mia macchina). Ma, naturalmente, il suo codice è molto più adatta a SSE e non è che incline a effetti di latenza della memoria.

La maggior parte degli approcci descritti nelle altre risposte qui o non correttamente rotonda sulla conversione da float a metà, buttare via subnormals che è un problema dal 2 ** - 14 diventa il tuo più piccolo numero diverso da zero, o fare le cose sfortunate con Inf / NaN. Inf è anche un problema in quanto il maggior numero finito in mezzo è un po 'inferiore a 2 ^ 16. OpenEXR era inutilmente lento e complicato, ultima volta che ho guardato. Un approccio corretto veloce utilizzerà il FPU per eseguire la conversione, sia come istruzione diretta, o utilizzando l'hardware di arrotondamento FPU a fare la cosa giusta accadere. Qualsiasi mezzo di galleggiare conversione dovrebbe essere più lento di una tabella di ricerca elemento 2 ^ 16.

I seguenti sono difficili da battere:

su OS X / iOS, è possibile utilizzare vImageConvert_PlanarFtoPlanar16F e vImageConvert_Planar16FtoPlanarF. Vedere Accelerate.framework.

Intel Ivybridge aggiunto istruzioni SSE per questo. Vedere f16cintrin.h. Istruzioni simili sono stati aggiunti alla ISA ARM per Neon. Vedere vcvt_f32_f16 e vcvt_f16_f32 in arm_neon.h. Su iOS è necessario utilizzare l'arm64 o armv7s arco per ottenere l'accesso ad essi.

Questo codice converte un numero in virgola mobile a 32 bit a 16 bit e indietro.

#include <x86intrin.h>
#include <iostream>

int main()
{
    float f32;
    unsigned short f16;
    f32 = 3.14159265358979323846;
    f16 = _cvtss_sh(f32, 0);
    std::cout << f32 << std::endl;
    f32 = _cvtsh_ss(f16);
    std::cout << f32 << std::endl;
    return 0;
}

Ho provato con il processore Intel ICPC 16.0.2:

$ icpc a.cpp

g ++ 7.3.0:

$ g++ -march=native a.cpp

e clang ++ 6.0.0:

$ clang++ -march=native a.cpp

Esso stampa:

$ ./a.out
3.14159
3.14062

La documentazione su questi intrinseche è disponibile all'indirizzo:

https://software.intel.com/en-us/node/524287

https://clang.llvm.org/doxygen/f16cintrin_8h.html

Questa conversione per virgola mobile 16-a-32-bit è abbastanza veloce per i casi in cui non si dispone per tenere conto di infiniti o NaN, e può accettare denormals-as-zero (DAZ). Cioè è adatto per i calcoli sensibili alle prestazioni, ma si dovrebbe stare attenti divisione per zero se vi aspettate di incontrare denormals.

Si noti che questo è più adatto per x86 o altre piattaforme che hanno mosse condizionate o "Imposta se" equivalenti.

  1. Striscia il bit del segno fuori l'ingresso
  2. Allineare il bit più significativo della mantissa nel bit 22
  3. Regola il bias esponente
  4. Imposta bit a tutti zero se l'esponente ingresso è zero
  5. po Reinserire segno

Il contrario vale per la singola a mezza precisione, con alcune aggiunte.

void float32(float* __restrict out, const uint16_t in) {
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;

    t1 = in & 0x7fff;                       // Non-sign bits
    t2 = in & 0x8000;                       // Sign bit
    t3 = in & 0x7c00;                       // Exponent

    t1 <<= 13;                              // Align mantissa on MSB
    t2 <<= 16;                              // Shift sign bit into position

    t1 += 0x38000000;                       // Adjust bias

    t1 = (t3 == 0 ? 0 : t1);                // Denormals-as-zero

    t1 |= t2;                               // Re-insert sign bit

    *((uint32_t*)out) = t1;
};

void float16(uint16_t* __restrict out, const float in) {
    uint32_t inu = *((uint32_t*)&in);
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;

    t1 = inu & 0x7fffffff;                 // Non-sign bits
    t2 = inu & 0x80000000;                 // Sign bit
    t3 = inu & 0x7f800000;                 // Exponent

    t1 >>= 13;                             // Align mantissa on MSB
    t2 >>= 16;                             // Shift sign bit into position

    t1 -= 0x1c000;                         // Adjust bias

    t1 = (t3 > 0x38800000) ? 0 : t1;       // Flush-to-zero
    t1 = (t3 < 0x8e000000) ? 0x7bff : t1;  // Clamp-to-max
    t1 = (t3 == 0 ? 0 : t1);               // Denormals-as-zero

    t1 |= t2;                              // Re-insert sign bit

    *((uint16_t*)out) = t1;
};

Si noti che è possibile modificare la 0x7bff costante 0x7c00 per farlo traboccare all'infinito.

per il codice sorgente Vedere GitHub .

Ho trovato un implementazione della conversione da mezzo galleggiante singolo formato galleggiante e indietro con l'utilizzo di AVX2. Ci sono molto più veloce di implementazione software di questi algoritmi. Spero che sarà utile.

32 bit float alla conversione galleggiante 16 bit:

#include <immintrin.h"

inline void Float32ToFloat16(const float * src, uint16_t * dst)
{
    _mm_storeu_si128((__m128i*)dst, _mm256_cvtps_ph(_mm256_loadu_ps(src), 0));
}

void Float32ToFloat16(const float * src, size_t size, uint16_t * dst)
{
    assert(size >= 8);

    size_t fullAlignedSize = size&~(32-1);
    size_t partialAlignedSize = size&~(8-1);

    size_t i = 0;
    for (; i < fullAlignedSize; i += 32)
    {
        Float32ToFloat16(src + i + 0, dst + i + 0);
        Float32ToFloat16(src + i + 8, dst + i + 8);
        Float32ToFloat16(src + i + 16, dst + i + 16);
        Float32ToFloat16(src + i + 24, dst + i + 24);
    }
    for (; i < partialAlignedSize; i += 8)
        Float32ToFloat16(src + i, dst + i);
    if(partialAlignedSize != size)
        Float32ToFloat16(src + size - 8, dst + size - 8);
}

16-bit float alla conversione galleggiante 32 bit:

#include <immintrin.h"

inline void Float16ToFloat32(const uint16_t * src, float * dst)
{
    _mm256_storeu_ps(dst, _mm256_cvtph_ps(_mm_loadu_si128((__m128i*)src)));
}

void Float16ToFloat32(const uint16_t * src, size_t size, float * dst)
{
    assert(size >= 8);

    size_t fullAlignedSize = size&~(32-1);
    size_t partialAlignedSize = size&~(8-1);

    size_t i = 0;
    for (; i < fullAlignedSize; i += 32)
    {
        Float16ToFloat32<align>(src + i + 0, dst + i + 0);
        Float16ToFloat32<align>(src + i + 8, dst + i + 8);
        Float16ToFloat32<align>(src + i + 16, dst + i + 16);
        Float16ToFloat32<align>(src + i + 24, dst + i + 24);
    }
    for (; i < partialAlignedSize; i += 8)
        Float16ToFloat32<align>(src + i, dst + i);
    if (partialAlignedSize != size)
        Float16ToFloat32<false>(src + size - 8, dst + size - 8);
}

Il problema è vecchio ed è già stato risposto, ma ho pensato che sarebbe stato degno di nota una fonte di C aperta ++ libreria che può creare a 16 bit IEEE carri metà di precisione conformi e ha una classe che agisce praticamente identico alla costruita nel tipo float , ma con 16 bit anziché 32. è il "metà" classe del OpenEXR biblioteca. Il codice è sotto una licenza stile BSD permissiva. Io non credo che abbia tutte le dipendenze al di fuori della libreria standard.

Ho avuto questo stesso problema esatto, e ha trovato questo link molto utile. Basta importare il file "ieeehalfprecision.c" nel progetto e usare in questo modo:

float myFloat = 1.24;
uint16_t resultInHalf;
singles2halfp(&resultInHalf, &myFloat, 1); // it accepts a series of floats, so use 1 to input 1 float

// an example to revert the half float back
float resultInSingle;
halfp2singles(&resultInSingle, &resultInHalf, 1);

Ho anche cambiare qualche codice (vedi il commento dall'autore (James Tursa) nel link):

#define INT16_TYPE int16_t 
#define UINT16_TYPE uint16_t 
#define INT32_TYPE int32_t 
#define UINT32_TYPE uint32_t
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top