C语言中float32与float16(uint16_t)互转及边缘情况处理
C语言实现float32与float16(uint16_t存储)双向转换(含边缘情况处理)
核心原理
先明确两种浮点数的二进制格式:
- float32:1位符号位 + 8位指数位(偏移量127) + 23位尾数位
- float16:1位符号位 + 5位指数位(偏移量15) + 10位尾数位
我们用uint16_t存储float16的原始二进制位,无需进行算术运算,仅保证二进制表示完全符合float16标准。
实现代码
依赖头文件
#include <stdint.h> #include <stdbool.h>
辅助宏与类型定义
typedef uint16_t float16_t; // float32字段提取宏 #define F32_SIGN_MASK 0x80000000U #define F32_EXP_MASK 0x7F800000U #define F32_MANT_MASK 0x007FFFFFU #define F32_EXP_SHIFT 23U #define F32_EXP_BIAS 127U // float16字段提取宏 #define F16_SIGN_MASK 0x8000U #define F16_EXP_MASK 0x7C00U #define F16_MANT_MASK 0x03FFU #define F16_EXP_SHIFT 10U #define F16_EXP_BIAS 15U
float32转float16(uint16_t)
float16_t float32_to_float16(float f) { union { float f32; uint32_t u32; } conv = {.f32 = f}; uint32_t u32 = conv.u32; uint16_t sign = (u32 & F32_SIGN_MASK) >> 16; uint32_t exp32 = (u32 & F32_EXP_MASK) >> F32_EXP_SHIFT; uint32_t mant32 = u32 & F32_MANT_MASK; // 处理无穷大/NaN if (exp32 == 0xFF) { if (mant32 == 0) { // 无穷大:指数全1,尾数0 return sign | 0x7C00U; } else { // NaN:保留尾数高10位,强制转为QNaN(避免陷阱) uint16_t mant16 = (mant32 >> 13) | 0x0200U; return sign | 0x7C00U | mant16; } } // 处理正负零 if (exp32 == 0 && mant32 == 0) { return sign; } // 处理正常数值 int32_t exp16 = exp32 - F32_EXP_BIAS + F16_EXP_BIAS; if (exp16 <= 0) { // 下溢:转为非规格化数或零 if (exp16 <= -10) { return sign; } uint32_t mant_shifted = mant32 | (1U << 23); uint16_t mant16 = mant_shifted >> (23 + 1 - exp16); return sign | mant16; } else if (exp16 >= 0x1F) { // 上溢:转为无穷大 return sign | 0x7C00U; } else { // 规格化数:截断尾数到10位(如需四舍五入可取消下方注释) uint16_t mant16 = mant32 >> 13; // 四舍五入逻辑: // if ((mant32 & 0x1FFFU) > 0x1000U) mant16 += 1; // if (mant16 & 0x0400U) { mant16 = 0; exp16 += 1; } return sign | ((uint16_t)exp16 << F16_EXP_SHIFT) | mant16; } }
float16转float32
// 手动实现前导零计数(替代GCC内置__builtin_clz,兼容WebAssembly) static int count_leading_zeros(uint16_t x) { if (x == 0) return 16; int cnt = 0; while (!(x & 0x8000U)) { cnt++; x <<= 1; } return cnt; } float float16_to_float32(float16_t f16) { union { float f32; uint32_t u32; } conv; uint16_t sign = f16 & F16_SIGN_MASK; uint16_t exp16 = (f16 & F16_EXP_MASK) >> F16_EXP_SHIFT; uint16_t mant16 = f16 & F16_MANT_MASK; uint32_t u32 = 0; // 处理无穷大/NaN if (exp16 == 0x1F) { u32 |= (uint32_t)sign << 16; u32 |= 0x7F800000U; if (mant16 != 0) { u32 |= (uint32_t)mant16 << 13; } conv.u32 = u32; return conv.f32; } // 处理正负零 if (exp16 == 0 && mant16 == 0) { u32 |= (uint32_t)sign << 16; conv.u32 = u32; return conv.f32; } // 处理正常数值 if (exp16 == 0) { // 非规格化数转换 int shift = count_leading_zeros(mant16) - 6; int32_t exp32 = F32_EXP_BIAS - F16_EXP_BIAS + 1 - shift; uint32_t mant32 = (uint32_t)mant16 << shift; u32 |= (uint32_t)sign << 16; u32 |= (uint32_t)exp32 << F32_EXP_SHIFT; u32 |= mant32; } else { // 规格化数转换 int32_t exp32 = exp16 - F16_EXP_BIAS + F32_EXP_BIAS; uint32_t mant32 = (uint32_t)mant16 << 13; u32 |= (uint32_t)sign << 16; u32 |= (uint32_t)exp32 << F32_EXP_SHIFT; u32 |= mant32; } conv.u32 = u32; return conv.f32; }
关键边缘情况处理说明
- 无穷大:严格对应两种格式的无穷大二进制表示,保留符号位
- NaN:强制生成QNaN(尾数最高位为1),避免触发硬件陷阱,同时保留原始尾数的有效信息
- 零:保留正负零的符号位,指数和尾数均置0
- 下溢/上溢:float32超出float16范围时,分别转为零或无穷大
- 非规格化数:正确处理两种格式之间的非规格化转换,保证二进制表示的一致性
内容的提问来源于stack exchange,提问作者juffma
相关产品推荐
相关产品推荐

