将数字从单精度浮点表示转换为半精度浮点

希望这里有关。

我有一个代码,我必须在半精度浮点表示数字上工作。 为了达到这个目的,我创建了自己的C ++类fp16,所有与这种types相关的运算符(算术逻辑,关系)与我的自定义函数重载,同时使用具有半精度浮点数的单精度浮点数 。

半精度浮点= 1符号位,5个指数位,10个有效位= 16位

单精度浮点= 1符号位,8个指数位,23个有效位= 32位

那么我怎么做从单精度浮点数转换为半精度浮点数:

对于有效位 – 我使用截断,即从32位松散13位获得10位有效数字半精度浮点数。

我该怎么处理指数位。 我如何从8个指数位到5个指数位?

任何好的阅读材料都会有帮助。

假设一个正常数(小数正常数足够小以至于它们可以安全地设置为0,无限大,零,负零和NaN需要专门处理),则需要从原始浮点数的指数中减去指数偏移(对于32位float为127),然后重新添加新格式的指数偏移量(对于16位half值为15)。

  • 如果结果在1到30之间,则可以使用它。
  • 如果它是-9到0,你可以尝试构建一个低于正常的数字 。 或者,考虑到在某些平台上与它们相关的性能损失,您可能不会,也可以简单地将该数字设置为零,从而切断结果中的值空间。 哪个select更好,主要取决于你的目标硬件,但你应该知道有一个select。
  • 如果是31或者更多,那么你有一个溢出,并且应该构造一个无穷大或负无穷大(如果指数设置为31,而有效位全部设置为零)。
  • 在所有其他情况下(即,如果得到的指数为-10或更小),则数字太小而不能用半精度浮点数表示。 结果应该设置为零或负零。

在所有情况下:如果您决定舍入有效数字而不是截断,请小心指数上移。 目前你正在截断它(四舍五入到零),所以这不是一个问题。

这是我的两个实现。 第一个使用分支,这可能是一个有序CPU的问题,但使用很less的内存:

 /* This method is faster than the OpenEXR implementation (very often * used, eg. in Ogre), with the additional benefit of rounding, inspired * by James Tursa's half-precision code. */ static inline uint16_t float_to_half_branch(uint32_t x) { uint16_t bits = (x >> 16) & 0x8000; /* Get the sign */ uint16_t m = (x >> 12) & 0x07ff; /* Keep one extra bit for rounding */ unsigned int e = (x >> 23) & 0xff; /* Using int is faster here */ /* If zero, or denormal, or exponent underflows too much for a denormal * half, return signed zero. */ if (e < 103) return bits; /* If NaN, return NaN. If Inf or exponent overflow, return Inf. */ if (e > 142) { bits |= 0x7c00u; /* If exponent was 0xff and one mantissa bit was set, it means NaN, * not Inf, so make sure we set one mantissa bit too. */ bits |= e == 255 && (x & 0x007fffffu); return bits; } /* If exponent underflows but not too much, return a denormal */ if (e < 113) { m |= 0x0800u; /* Extra rounding may overflow and set mantissa to 0 and exponent * to 1, which is OK. */ bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1); return bits; } bits |= ((e - 112) << 10) | (m >> 1); /* Extra rounding. An overflow will set mantissa to 0 and increment * the exponent, which is OK. */ bits += m & 1; return bits; } 

第二个只使用表,它使用一些内存,因此可能遭受caching未命中,如果只是不时使用:

 /* These macros implement a finite iterator useful to build lookup * tables. For instance, S64(0) will call S1(x) for all values of x * between 0 and 63. * Due to the exponential behaviour of the calls, the stress on the * compiler may be important. */ #define S4(x) S1((x)), S1((x)+1), S1((x)+2), S1((x)+3) #define S16(x) S4((x)), S4((x)+4), S4((x)+8), S4((x)+12) #define S64(x) S16((x)), S16((x)+16), S16((x)+32), S16((x)+48) #define S256(x) S64((x)), S64((x)+64), S64((x)+128), S64((x)+192) #define S1024(x) S256((x)), S256((x)+256), S256((x)+512), S256((x)+768) /* Lookup table-based algorithm from “Fast Half Float Conversions” * by Jeroen van der Zijp, November 2008. No rounding is performed, * and some NaN values may be incorrectly converted to Inf. */ static inline uint16_t float_to_half_nobranch(uint32_t x) { static uint16_t const basetable[512] = { #define S1(i) (((i) < 103) ? 0x0000 : \ ((i) < 113) ? 0x0400 >> (113 - (i)) : \ ((i) < 143) ? ((i) - 112) << 10 : 0x7c00) S256(0), #undef S1 #define S1(i) (0x8000 | (((i) < 103) ? 0x0000 : \ ((i) < 113) ? 0x0400 >> (113 - (i)) : \ ((i) < 143) ? ((i) - 112) << 10 : 0x7c00)) S256(0), #undef S1 }; static uint8_t const shifttable[512] = { #define S1(i) (((i) < 103) ? 24 : \ ((i) < 113) ? 126 - (i) : \ ((i) < 143 || (i) == 255) ? 13 : 24) S256(0), S256(0), #undef S1 }; uint16_t bits = basetable[(x >> 23) & 0x1ff]; bits |= (x & 0x007fffff) >> shifttable[(x >> 23) & 0x1ff]; return bits; } 

几年前,我一直在解决同样的问题 – 我想在OpenGL中使用Half Float扩展,所以我需要将Float32转换为Float16。 我find了这个代码的地方,所以我希望它也能帮助你:

 #define F16_EXPONENT_BITS 0x1F #define F16_EXPONENT_SHIFT 10 #define F16_EXPONENT_BIAS 15 #define F16_MANTISSA_BITS 0x3ff #define F16_MANTISSA_SHIFT (23 - F16_EXPONENT_SHIFT) #define F16_MAX_EXPONENT (F16_EXPONENT_BITS << F16_EXPONENT_SHIFT) GLushort F32toF16(GLfloat val) { GLuint f32 = (*(GLuint *) &val); GLushort f16 = 0; /* Decode IEEE 754 little-endian 32-bit floating-point value */ int sign = (f32 >> 16) & 0x8000; /* Map exponent to the range [-127,128] */ int exponent = ((f32 >> 23) & 0xff) - 127; int mantissa = f32 & 0x007fffff; if (exponent == 128) { /* Infinity or NaN */ f16 = sign | F16_MAX_EXPONENT; if (mantissa) f16 |= (mantissa & F16_MANTISSA_BITS); } else if (exponent > 15) { /* Overflow - flush to Infinity */ f16 = sign | F16_MAX_EXPONENT; } else if (exponent > -15) { /* Representable value */ exponent += F16_EXPONENT_BIAS; mantissa >>= F16_MANTISSA_SHIFT; f16 = sign | exponent << F16_EXPONENT_SHIFT | mantissa; } else { f16 = sign; } return f16; }