Pregunta

Necesito una biblioteca multiplataforma / algoritmo que convierte entre los números de punto flotante de 16 bits y de 32 bits. No necesito a realizar operaciones matemáticas con los números de 16 bits; Sólo hay que disminuir el tamaño de la flota de 32 bits para que puedan ser enviados a través de la red. Estoy trabajando en C ++.

Yo entiendo la cantidad de precisión que se esté perdiendo, pero está bien para mi aplicación.

El formato IEEE de 16 bits sería grande.

¿Fue útil?

Solución

std::frexp extrae la mantisa y exponente de flotadores o dobles normales - - a continuación, tiene que decidir qué hacer con exponentes que son demasiado grandes para caber en un flotador media de precisión (saturar ...?), los ajustes correspondientes, y poner el número medio de precisión juntos. Este artículo tiene código fuente en C que le muestre cómo realizar la conversión.

Otros consejos

La conversión completa de precisión simple a la mitad de precisión. Esta es una copia directa de mi versión de SSE, por lo que es rama menos. Se hace uso del hecho de que en GCC (-true == ~ 0), puede ser cierto para VisualStudio también, pero, no tengo 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;
        }
    };

Así que eso es mucho para tomar en pero, que se encarga de todos los valores subnormales, ambos infinitos, tranquila NANS, señalización NaNs, y el cero negativo. Por supuesto, no siempre se necesita el apoyo total del IEEE. Así comprimir flotadores genéricos:

    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;
        }

    };

Esto obliga a todos los valores dentro del rango aceptado, no hay soporte para NANS, infinitos o cero negativo. Epsilon es el menor valor permisible de la gama. La precisión es el número de bits de precisión para retener del flotador. Si bien hay una gran cantidad de ramas arriba, todos ellos son estáticos y se almacenan en caché por el predictor de saltos en la CPU.

Por supuesto, si sus valores no requieren resolución logarítmica cercanos a cero. Entonces linealizar a un formato de punto fijo es mucho más rápido, como ya se mencionó.

utilizo el FloatCompressor (versión SSE) en la biblioteca de gráficos para reducir el tamaño de los valores de color de flotador lineal en la memoria. flotadores comprimidos tienen la ventaja de crear pequeñas tablas de consulta para las funciones que consumen tiempo, como la corrección gamma o trascendentales. La compresión de valores sRGB lineales reduce a un máximo de 12 bits o un valor máximo de 3.011, que es grande para un tamaño de tabla de consulta para a / de sRGB.

Teniendo en cuenta sus necesidades (-1000, 1000), tal vez sería mejor utilizar una representación de punto fijo.

//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;
}

Esto le dará a la exactitud 0,05 cercano. Si cambia a 20000 SHORT_MAX obtendrá un poco más precisión, pero algunos números enteros terminarán como decimales en el otro extremo.

La mitad flote:
float f = ((h&0x8000)<<16) | (((h&0x7c00)+0x1C000)<<13) | ((h&0x03FF)<<13);
Flotar a la mitad:
uint32_t x = *((uint32_t*)&f);
uint16_t h = ((x>>16)&0x8000)|((((x&0x7f800000)-0x38000000)>>13)&0x7c00)|((x>>13)&0x03ff);

Si va a enviar un flujo de información a través, que probablemente podría hacer mejor que esto, especialmente si todo está en un rango constante, ya que su aplicación parece tener.

Enviar una pequeña cabecera, que simplemente consiste en un mínimo y un máximo float32, entonces se puede enviar a través de su información como un valor de interpolación de 16 bits entre los dos. Como también dice que la precisión no es un gran problema, incluso se podría enviar 8 bits a la vez.

Su valor sería algo así como, en el momento de la reconstrucción:

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

Espero que ayude.

-Tom

Esta pregunta es ya un poco viejo, pero en aras de la exhaustividad, también puede echar un vistazo a este documento para la conversión media-a-flotador y el flotador-a-media.

Utilizan un enfoque basado en tablas sin sucursales con relativamente pequeñas tablas de consulta. Es completamente IEEE-conformes e incluso supera a las rutinas de conversión sin sucursales IEEE-conformantes de Phernost en el rendimiento (al menos en mi máquina). Pero, por supuesto, su código no se adapta mucho mejor a la SSE y es que propensos a los efectos de latencia de memoria.

La mayoría de los enfoques descritos en las otras respuestas aquí, o bien no redonda correctamente en la conversión de flotación a la mitad, tiro de subnormales que es un problema desde el 2 ** - 14 se convierte en su no-cero número más pequeño, o hacer cosas desafortunadas con INF / NaN. Inf es también un problema debido a que el número finito más grande de la mitad es un poco menos de 2 ^ 16. OpenEXR era innecesariamente lento y complicado, última vez que miré. Un enfoque rápido correcta usará la FPU para hacer la conversión, ya sea como una instrucción directa, o usando el hardware redondeo FPU para hacer lo correcto suceda. Cualquier medio para flotar conversión no debe ser más lento que una mesa de elemento de búsqueda de 2 ^ 16.

Los siguientes son difícil de superar:

En OS X / iOS, puede utilizar vImageConvert_PlanarFtoPlanar16F y vImageConvert_Planar16FtoPlanarF. Ver Accelerate.framework.

Intel ivybridge añadió instrucciones SSE para esto. Ver f16cintrin.h. Instrucciones similares se añadieron a la ISA ARM de neón. Ver vcvt_f32_f16 y vcvt_f16_f32 en arm_neon.h. En iOS tendrá que utilizar el arm64 o armv7s arco para obtener acceso a ellos.

Este código convierte un número de coma flotante de 32 bits a 16 bits y la espalda.

#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;
}

He probado con el procesador Intel icpc 16.0.2:

$ icpc a.cpp

g ++ 7.3.0:

$ g++ -march=native a.cpp

y sonido metálico ++ 6.0.0:

$ clang++ -march=native a.cpp

Imprime:

$ ./a.out
3.14159
3.14062

Documentación sobre estas funciones intrínsecas está disponible en:

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

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

Esta conversión de punto flotante de 16 a 32 bits es bastante rápido para los casos en que no tiene que dar cuenta de los infinitos o NaNs, y puede aceptar denormals-como-cero (DAZ). Es decir. es adecuado para los cálculos sensibles al rendimiento, pero se debe tener cuidado con la división por cero si se espera encontrar denormals.

Tenga en cuenta que esta es la más adecuada para x86 u otras plataformas que tienen movimientos condicionales o "establecidas si" equivalentes.

  1. Gaza el bit de signo de la entrada
  2. Alinear el bit más significativo de la mantisa en el bit 22
  3. Ajuste el sesgo exponente
  4. set bits a todo ceros si el exponente entrada es cero
  5. bit de signo Vuelva a insertar

Lo contrario se aplica para-solo-a-media de precisión, con algunas adiciones.

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;
};

Tenga en cuenta que puede cambiar el 0x7bff constante para 0x7c00 para que se desborde hasta el infinito.

GitHub para el código fuente.

He encontrado un de la conversión a partir de mediados de flotador al formato de un solo flotador y vuelta con el uso de AVX2. Hay mucho más rápido que el software de aplicación de estos algoritmos. Espero que sea útil.

32-bit de flotador a la conversión flotador 16 bits:

#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 de flotador a la conversión flotador 32 bits:

#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);
}

Tuve este mismo problema exacto, y encontré este enlace muy útil. Sólo tiene que importar el archivo "ieeehalfprecision.c" en su proyecto y utilizar de esta manera:

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);

Yo también cambiar algo de código (Ver el comentario por el autor (James Tursa) en el enlace):

#define INT16_TYPE int16_t 
#define UINT16_TYPE uint16_t 
#define INT32_TYPE int32_t 
#define UINT32_TYPE uint32_t
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top