32位到16位浮点转换

我需要一个可以在32位和16位浮点数之间转换的跨平台库/algorithm。 我不需要用16位数字来进行math运算; 我只需要减小32位浮点数的大小,以便通过networking发送。 我正在使用C ++。

我明白我会失去多less精度,但这对我的应用程序是可以的。

IEEE 16位格式将会很棒。

std::frexp从正常的浮点数或双std::frexp提取有效数和指数 – 然后您需要决定如何处理太大的指数以适应半精度浮点数(饱和…?),并相应地进行调整,以及把半精度数字放在一起。 本文有C源代码向您展示如何执行转换。

从单精度到半精度的完整转换。 这是从我的SSE版本直接复制,所以它是分支。 它利用了在GCC(-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; vf = value; uint32_t sign = v.si & signN; v.si ^= sign; sign >>= shiftSign; // logical shift s.si = mulN; s.si = sf * vf; // 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; sf *= v.si; int32_t mask = -(norC > v.si); v.si <<= shift; v.si ^= (s.si ^ v.si) & mask; v.si |= sign; return vf; } }; 

所以这是非常重要的,但是它处理所有低于正常的值,包括无穷大,安静的NaN,信号NaN和负的零。 当然,全面的IEEE支持并不总是需要的。 所以压缩generics浮点数:

  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; vf = min; _minF = v.si; vf = epsilon; _epsF = v.si; vf = 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; vf = 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 vf; } uint32_t compress(float value) { Bits v; vf = 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 vf; } }; 

这迫使所有的值进入可接受的范围,不支持NaN,无穷或负零。 Epsilon是该范围内的最小允许值。 精度是从浮子上保留多less位精度。 虽然上面有很多分支,但它们都是静态的,并且会被CPU中的分支预测器caching。

当然,如果你的值不需要对数分辨率接近零。 然后将它们线性化为一个固定点格式要快得多,如前所述。

我在graphics库中使用FloatCompressor(SSE版本)来减less内存中线性浮点颜色值的大小。 压缩的浮点数的优点是可以为耗时的函数创build小型查找表,如伽马校正或超越性(transcendentals)。 压缩线性sRGB值可减less到12位的最大值或最大值3011,这对于sRGB的查找表大小非常有用。

考虑到你的需求(-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,则会获得更高的准确性,但是一些整数最终会以小数forms结束。

一半浮动:
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);

如果你发送信息stream,你可能会比这更好,尤其是如果你的应用程序似乎有一个一致的范围。

发送一个小的头文件,这个头文件只包含一个float32的最小值和最大值,然后你可以在两者之间传送你的信息作为16位插值。 正如你所说的,精度不是什么大问题,你甚至可以一次发送8位数据。

在重build的时候,你的价值会是这样的:

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

希望有所帮助。

-Tom

这个问题已经有点老了,但为了完整起见,你也可以看看这篇文章中的半浮点和浮点到半点的转换。

他们使用无分表的表驱动方法,查找表相对较小。 它完全符合IEEE标准,甚至在性能上(至less在我的机器上)击败了Phernost的符合IEEE标准的无分支转换例程。 但是他的代码当然更适合于SSE,并不容易出现内存延迟效应。

大多数在这里的其他答案中描述的方法要么不正确地转换从一半到一半,丢弃subnormals这是一个问题,因为2 ** – 14成为最小的非零数字,或做不幸的事情与Inf / NaN的。 Inf也是一个问题,因为最大有限数的一半是小于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 arch来访问它们。

对于16位到32位浮点的转换非常快,对于无需考虑无穷大或NaN并且可以接受denormals-as-zero(DAZ)的情况。 也就是说,它适合对性能敏感的计算,但是如果您期望遇到反常规,您应该小心零除。

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

  1. 从input中去除符号位
  2. 将尾数的最高有效位与第22位alignment
  3. 调整指数偏差
  4. 如果input指数为零,则将位设置为全零
  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; }; 

请注意,您可以将常量0x7bff更改为0x7c00 ,使其溢出至无穷大。

有关源代码,请参阅GitHub 。

此代码将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; } 

我testing了英特尔icpc编译器版本16.0.2。 它打印:

 3.14159 3.14062 

有关这些内在因素的文档可在以下位置获得:

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

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

我已经find了一个从半浮点到单浮点格式的转换,以及使用AVX2的转换。 比这些algorithm的软件实现要快得多。 我希望这会有用。

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

问题是旧的,已经得到了回答,但我认为值得一提的是一个开源的C ++库,它可以创build16位符合IEEE的半精度浮点数,并且具有类似于内置浮点types的类,但是与16位而不是32位。这是OpenEXR库的“半”类 。 该代码是在宽松的BSD样式的许可证下。 我不相信它在标准库之外有任何依赖关系。

我有同样的确切问题,并发现这个链接非常有帮助。 只需将文件“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); 

我也改变了一些代码(参见作者(James Tursa)在链接中的评论):

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