32位到16位浮点转换(32-bit to 16-bit Floating Point Conver

2019-09-03 19:27发布

我需要一个跨平台的库/算法,将32位和16位浮点数字的互相转化。 我并不需要与16位数字执行数学; 我只需要减少32位的大小浮动,使他们能够在网络上发送。 我在C ++中工作。

我明白我是多么的精度会输球,但没关系我的应用程序。

在IEEE 16位格式将是巨大的。

Answer 1:

std::frexp提取有效数字和指数从正常的浮点或双精度-那么你需要决定如何处理太大而不能在半精度浮点(?饱和...),相应地调整指数做了,把半精度数一起。 这篇文章有C源代码向您展示如何执行转换。



Answer 2:

完成转换,从单精度到半精度。 这是从我的SSE版本直接复制,因此它的分支少。 它利用的事实是,在海湾合作委员会(-true ==〜0),可用于VisualStudio的是真实的太多,但,我没有副本。

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

所以这是一个很大采取,但它处理所有低于正常价值,无论是无穷大,安静的NaN,信号NaN,零和负零。 当然,并不总是需要完整的IEEE支持。 因此,压缩一般性花车:

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

    };

这迫使所有的值到可接受的范围内,NaN的无穷大或负零的支持。 小量的范围是从最小允许值。 精度是怎样的精度多少位从浮保留。 虽然有很多分支以上,他们都是静态的,将由CPU中的分支预测器缓存。

当然,如果你的价值观不要求对数分辨率接近零。 然后将它们线性化到固定点格式的速度要快得多,因为已经提到。

我用在图形库的FloatCompressor(SSE版本)用于减少存储器线性浮色值的大小。 压缩花车有创建耗时的函数小的查找表,如伽玛校正或超越数的优势。 压缩线性sRGB值降低到12位的最高或3011一最大值,这对于一个查找表的大小为从sRGB进行/是很大的。



Answer 3:

鉴于您的需求(-1000,1000),也许这将是更好地使用定点表示。

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

这会给你准确到最近的0.05。 如果更改20000 SHORT_MAX你会得到更多的准确性,但一些完整的数字最终将成为在另一端小数。



Answer 4:

半浮动:
float f = ((h&0x8000)<<16) | (((h&0x7c00)+0x1C000)<<13) | ((h&0x03FF)<<13);

浮到一半:
uint32_t x = *((uint32_t*)&f);
uint16_t h = ((x>>16)&0x8000)|((((x&0x7f800000)-0x38000000)>>13)&0x7c00)|((x>>13)&0x03ff);



Answer 5:

如果您跨发送的信息流,你也许可以做得比这更好,尤其是当一切都在一致的范围内,为您的应用程序似乎有。

送小头,只是由FLOAT32最小和最大的,那么你就可以在你的信息发送作为两者之间的16位插值。 正如你还说,精度没有太大问题的,你甚至可以一次发送8位。

你的价值会是这样的,在重建时间:

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

希望帮助。

-Tom



Answer 6:

这个问题已经有点老了,但为了完整起见,你也不妨来看看在本文中为半漂浮的和浮到一半的转换。

他们使用具有相对小的查找表的一个网点表驱动的方法。 这是完全IEEE符合的,甚至击败性能Phernost的IEEE-符合的网点转换例程(至少在我的机器上)。 但当然,他的代码是更适合于SSE,是不是容易发生内存延迟的影响。



Answer 7:

在大多数其他的答案在这里描述要么不正确轮从浮到一半的转换,扔掉次归这是一个问题,因为2 **的方法的 - 14成为您的最小非零数字,还是不幸的事情INF / NaN的。 天道酬勤也是一个问题,因为一半的最大有限数2 ^ 16有点少。 OpenEXR的是不必要的缓慢和复杂的,最后我看着它。 快速正确的方法将使用FPU来进行转换,无论是作为一个直接的指令,或使用FPU四舍五入硬件做出正确的事情发生。 任何半浮动转换应不大于2 ^ 16元件查找表慢。

以下是很难被击败的:

在OS X / iOS版,您可以使用vImageConvert_PlanarFtoPlanar16F和vImageConvert_Planar16FtoPlanarF。 见Accelerate.framework。

英特尔IvyBridge的加入SSE指令这一点。 见f16cintrin.h。 类似的指令被添加到霓虹灯的ARM ISA。 见arm_neon.h vcvt_f32_f16和vcvt_f16_f32。 在iOS上,您将需要使用arm64或armv7s拱来访问它们。



Answer 8:

该代码转换的32位浮点数到16位和背部。

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

我与英特尔ICPC 16.0.2测试:

$ icpc a.cpp

克++ 7.3.0:

$ g++ -march=native a.cpp

和铛++ 6.0.0:

$ clang++ -march=native a.cpp

它打印:

$ ./a.out
3.14159
3.14062

这些intrinsic文档,请访问:

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

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



Answer 9:

这种转换为16到32位浮点是相当快的,你没有考虑到无穷大或NaN,并且可以接受非正规数为为零(DAZ)的情况。 也就是说,它是适用于性能敏感的计算,但是你应该为零,如果你希望遇到非规格化提防师。

请注意,这是最适合x86或具有有条件的移动或“设置如果”等同其他平台。

  1. 剥离符号位关闭输入
  2. 对齐尾数的最显著位到第22位
  3. 调整指数偏差
  4. 设置位为全零如果输入指数为零
  5. 重新插入符号位

相反适用于单端至半精度,具有一定的补充。

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

请注意,您可以不断变化0x7bff0x7c00它溢出到无穷远。

见GitHub的源代码。



Answer 10:

我已经找到了实现转换,从半浮点单浮点格式和回用AVX2的。 有远远超过软件实现这些算法的速度更快。 我希望这将是有益的。

32位浮点16位浮点转换:

#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位浮子32位浮点转换:

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


Answer 11:

现在的问题是旧的,已经回答了,但我想这将是值得一提的一个开源C ++库,它可以创建16位符合IEEE半精度浮点和有作用几乎等同于内置的浮子式一类,但16位而不是32它是“半”类中的OpenEXR库 。 该代码是在BSD许可风格的许可证。 我不相信它有标准库之外的任何依赖关系。



Answer 12:

我有同样的确切问题,并发现该链接非常有帮助。 只需将文件“ieeehalfprecision.c”导入到项目中,并使用它像这样:

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

我也改变了一些代码(见链接中的作者(詹姆斯Tursa)的注释):

#define INT16_TYPE int16_t 
#define UINT16_TYPE uint16_t 
#define INT32_TYPE int32_t 
#define UINT32_TYPE uint32_t


文章来源: 32-bit to 16-bit Floating Point Conversion