我需要一个跨平台的库/算法,将32位和16位浮点数字的互相转化。 我并不需要与16位数字执行数学; 我只需要减少32位的大小浮动,使他们能够在网络上发送。 我在C ++中工作。
我明白我是多么的精度会输球,但没关系我的应用程序。
在IEEE 16位格式将是巨大的。
我需要一个跨平台的库/算法,将32位和16位浮点数字的互相转化。 我并不需要与16位数字执行数学; 我只需要减少32位的大小浮动,使他们能够在网络上发送。 我在C ++中工作。
我明白我是多么的精度会输球,但没关系我的应用程序。
在IEEE 16位格式将是巨大的。
std::frexp
提取有效数字和指数从正常的浮点或双精度-那么你需要决定如何处理太大而不能在半精度浮点(?饱和...),相应地调整指数做了,把半精度数一起。 这篇文章有C源代码向您展示如何执行转换。
完成转换,从单精度到半精度。 这是从我的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进行/是很大的。
鉴于您的需求(-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你会得到更多的准确性,但一些完整的数字最终将成为在另一端小数。
半浮动:
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);
如果您跨发送的信息流,你也许可以做得比这更好,尤其是当一切都在一致的范围内,为您的应用程序似乎有。
送小头,只是由FLOAT32最小和最大的,那么你就可以在你的信息发送作为两者之间的16位插值。 正如你还说,精度没有太大问题的,你甚至可以一次发送8位。
你的价值会是这样的,在重建时间:
float t = _t / numeric_limits<unsigned short>::max(); // With casting, naturally ;)
float val = h.min + t * (h.max - h.min);
希望帮助。
-Tom
这个问题已经有点老了,但为了完整起见,你也不妨来看看在本文中为半漂浮的和浮到一半的转换。
他们使用具有相对小的查找表的一个网点表驱动的方法。 这是完全IEEE符合的,甚至击败性能Phernost的IEEE-符合的网点转换例程(至少在我的机器上)。 但当然,他的代码是更适合于SSE,是不是容易发生内存延迟的影响。
在大多数其他的答案在这里描述要么不正确轮从浮到一半的转换,扔掉次归这是一个问题,因为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拱来访问它们。
该代码转换的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
这种转换为16到32位浮点是相当快的,你没有考虑到无穷大或NaN,并且可以接受非正规数为为零(DAZ)的情况。 也就是说,它是适用于性能敏感的计算,但是你应该为零,如果你希望遇到非规格化提防师。
请注意,这是最适合x86或具有有条件的移动或“设置如果”等同其他平台。
相反适用于单端至半精度,具有一定的补充。
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的源代码。
我已经找到了实现转换,从半浮点单浮点格式和回用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);
}
现在的问题是旧的,已经回答了,但我想这将是值得一提的一个开源C ++库,它可以创建16位符合IEEE半精度浮点和有作用几乎等同于内置的浮子式一类,但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);
我也改变了一些代码(见链接中的作者(詹姆斯Tursa)的注释):
#define INT16_TYPE int16_t
#define UINT16_TYPE uint16_t
#define INT32_TYPE int32_t
#define UINT32_TYPE uint32_t