Question

J'ai besoin d'une bibliothèque multi-plateforme / algorithme qui permet de convertir entre 32 bits et 16 bits nombres à virgule flottante. Je ne ai pas besoin de réaliser des opérations mathématiques avec les nombres 16 bits; J'ai juste besoin de diminuer la taille du 32 bits flotte afin qu'ils puissent être envoyés sur le réseau. Je travaille en C ++.

Je comprends combien la précision que je serais en train de perdre, mais c'est OK pour mon application.

Le format IEEE 16 bits serait grand.

Était-ce utile?

La solution

std::frexp extrait le significand et exposant des flotteurs normaux ou doubles - - alors vous devez décider quoi faire avec des exposants qui sont trop grands pour tenir dans un flotteur demi-précision (... sature?), ajuster et mettre le nombre de demi-précision ensemble. Cet article a C code source pour vous montrer comment effectuer la conversion.

Autres conseils

La conversion complète de simple précision de précision d'un demi. Ceci est une copie directe de ma version SSE, il est donc branche moins. Il utilise le fait que, dans GCC (-true == ~ 0), peut-être vrai pour VisualStudio aussi, mais, je n'ai pas une copie.

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

C'est donc beaucoup à prendre, mais il gère toutes les valeurs, les deux sous la normale, infinités Nans calme, signalisation Nans et zéro négatif. Bien sûr soutien IEEE est pas toujours nécessaire. Comprimant flotteurs génériques:

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

    };

Cela force toutes les valeurs dans la plage acceptée, aucun soutien pour Nans, ou infinités zéro négatif. Epsilon est la plus petite valeur admissible dans la plage. La précision est combien de bits de précision pour retenir du flotteur. Bien qu'il ya beaucoup de branches ci-dessus, ils sont tous statiques et seront mises en cache par le prédicteur de branchement dans la CPU.

Bien sûr, si vos valeurs ne nécessitent pas de résolution logarithmique proche de zéro. Ensuite, les linéariser à un format de point fixe est beaucoup plus rapide, comme on l'a déjà mentionné.

J'utilise le FloatCompressor (version SSE) dans la bibliothèque graphique pour réduire la taille des valeurs de couleur à flotteur linéaire dans la mémoire. flotteurs compressés ont l'avantage de créer de petites tables de consultation pour les fonctions prennent beaucoup de temps, comme la correction gamma ou transcendantaux. La compression des valeurs de sRGB linéaires réduit à un maximum de 12 bits ou une valeur maximale de 3011, ce qui est optimal pour une taille de table de consultation pour les vers / depuis sRGB.

Étant donné vos besoins (-1000, 1000), il serait peut-être préférable d'utiliser une représentation point fixe.

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

Cela vous donnera une précision à 0,05 le plus proche. Si vous changez 20000 à SHORT_MAX, vous aurez un peu plus de précision, mais certains nombres entiers finiront en décimales sur l'autre extrémité.

La moitié de flotter:
float f = ((h&0x8000)<<16) | (((h&0x7c00)+0x1C000)<<13) | ((h&0x03FF)<<13);
Float à moitié:
uint32_t x = *((uint32_t*)&f);
uint16_t h = ((x>>16)&0x8000)|((((x&0x7f800000)-0x38000000)>>13)&0x7c00)|((x>>13)&0x03ff);

Si vous envoyez un flux d'informations à travers, vous pourriez probablement faire mieux que cela, surtout si tout est dans une gamme cohérente, que votre application semble avoir.

Envoyer un petit en-tête, qui consiste seulement d'un minimum et maximum float32, vous pouvez envoyer à travers vos informations en tant que valeur d'interpolation 16 bits entre les deux. Comme vous le dites aussi que la précision est pas vraiment un problème, vous pouvez même envoyer 8bits à la fois.

Votre valeur serait quelque chose comme, au moment de la reconstruction:

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

L'espoir qui aide.

-Tom

Cette question est déjà un peu vieux, mais par souci d'exhaustivité, vous pourriez aussi jeter un oeil à ce papier pour moitié à flotteur et le flotteur à moitié conversion.

Ils utilisent une approche axée sur la table avec relativement petites branches sur des tables de consultation. Il est complètement IEEE-et bat même conforme routines de conversion ébranchés IEEE-de Phernost dans conforme à l 'exécution (au moins sur ma machine). Mais bien sûr, son code est beaucoup mieux adapté à SSE et pas sujettes à des effets de latence de la mémoire.

La plupart des approches décrites dans les autres réponses ici, soit ne pas rond correctement sur la conversion du flotteur à moitié, jeter ce qui est un aux anormaux problème depuis 2 ** - 14 devient votre plus petit nombre non nul, ou faire des choses malheureuses avec Inf / NNA. Inf est aussi un problème parce que le plus grand nombre fini de moitié est un peu moins de 2 ^ 16. OpenEXR était inutilement lent et compliqué, enfin je l'ai regardé. Une approche rapide correcte utilisera la FPU pour effectuer la conversion, soit comme une instruction directe, ou en utilisant le matériel d'arrondi FPU pour faire la bonne chose arriver. Toute moitié flotter conversion ne doit pas être plus lent que d'une table de recherche de l'élément 2 ^ 16.

Les éléments suivants sont difficiles à battre:

Sur OS X / iOS, vous pouvez utiliser vImageConvert_PlanarFtoPlanar16F et vImageConvert_Planar16FtoPlanarF. Voir Accelerate.framework.

Intel Ivybridge ajouté instructions SSE pour cela. Voir f16cintrin.h. Des instructions similaires ont été ajoutées à l'ARM ISA pour Neon. Voir vcvt_f32_f16 et vcvt_f16_f32 à arm_neon.h. Sur iOS, vous devrez utiliser le arm64 ou armv7s ARCH pour y avoir accès.

Ce code convertit un nombre à virgule flottante de 32 bits à 16 bits et à l'arrière.

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

Je l'ai testé avec le processeur Intel ICPC 16.0.2:

$ icpc a.cpp

g ++ 7.3.0:

$ g++ -march=native a.cpp

et clang ++ 6.0.0:

$ clang++ -march=native a.cpp

Il imprime:

$ ./a.out
3.14159
3.14062

Documentation sur ces intrinsics est disponible à l'adresse:

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

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

Cette conversion pour 16 à 32 bits à virgule flottante est assez rapide pour les cas où vous n'avez pas compte de infinités ou NaN, et peut accepter dénormalisés-comme zéro (DAZ). C'est à dire. il convient pour les calculs sensibles aux performances, mais vous devez prendre garde de la division par zéro si vous vous attendez à rencontrer dénormalisés.

Notez que ceci est le plus approprié pour x86 ou d'autres plates-formes qui ont mouvements conditionnels ou « set si » équivalents.

  1. Strip le bit de signe de l'entrée
  2. Aligner le bit le plus significatif de la mantisse au bit 22
  3. Régler le biais de l'exposant
  4. Set bits à zéro si tout l'exposant d'entrée est égal à zéro
  5. bit de signe Re-insert

L'inverse applique à simple précision à la moitié, avec des ajouts.

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

Notez que vous pouvez changer la 0x7bff constante 0x7c00 pour un débordement à l'infini.

Voir GitHub pour le code source.

J'ai trouvé un de conversion de demi-flotteur au format simple flotteur et retour à l'utilisation de AVX2. Il y a beaucoup plus rapide que la mise en œuvre du logiciel de ces algorithmes. J'espère que ce sera utile.

flotteur 32 bits pour la conversion du flotteur 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);
}

flotteur 16-bit à la conversion du flotteur 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);
}

La question est ancienne et a déjà répondu, mais je pensais que ce serait utile de mentionner une bibliothèque C ++ open source qui peut créer des flotteurs de précision 16bit moitié conforme IEEE et a une classe qui agit à peu près identique au type construit à flotteur , mais au lieu de 16 bits 32. Il est classe « moitié » de la OpenEXR bibliothèque . Le code est sous licence permissive de type BSD. Je ne crois pas qu'il a des dépendances à l'extérieur de la bibliothèque standard.

J'ai eu ce même problème, et trouvé ce lien très utile. Il suffit d'importer le fichier « ieeehalfprecision.c » dans votre projet et de l'utiliser comme ceci:

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

Je change également un code (Voir le commentaire de l'auteur (James Tursa) dans le lien):

#define INT16_TYPE int16_t 
#define UINT16_TYPE uint16_t 
#define INT32_TYPE int32_t 
#define UINT32_TYPE uint32_t
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top