vrecpeq_f32 ARM NEON 内联函数的C/C++参考实现如何编写?
可复现vrecpeq_f32运算结果的C/C++参考实现
可以完全复现,ARM架构规范明确规定了该指令的近似算法,不需要依赖硬件就能用纯C/C++实现逻辑:
#include <stdint.h> #include <string.h> float vrecpeq_f32_ref(float x) { // 提取IEEE754单精度浮点数的二进制位 uint32_t x_bits; memcpy(&x_bits, &x, sizeof(x)); // 分离符号位、指数位、尾数位 uint32_t sign = x_bits >> 31; int32_t exp = (x_bits >> 23) & 0xFF; uint32_t frac = x_bits & 0x7FFFFF; // 处理特殊值:0、无穷、NaN的情况按ARM规范返回对应值 if (exp == 0) { // 输入是0或者次正规数,返回无穷 uint32_t res_bits = (sign << 31) | (0xFF << 23); float res; memcpy(&res, &res_bits, sizeof(res)); return res; } if (exp == 0xFF) { // 输入是无穷或者NaN if (frac == 0) { // 无穷,返回0带对应符号 uint32_t res_bits = (sign << 31); float res; memcpy(&res, &res_bits, sizeof(res)); return res; } else { // NaN,返回原NaN return x; } } // 正常数计算:指数 = 253 - 原指数(倒数的指数是-原指数,加上127的偏移量换算后得到该值) int32_t res_exp = 253 - exp; // 取尾数的高7位 + 指数的最低位,共8位作为查表索引,为ARM规范定义的索引规则 uint32_t table_idx = ((exp & 1) << 7) | (frac >> 16); // ARM规范定义的256项倒数近似表,高8位对应结果尾数的高8位 static const uint16_t recp_table[256] = { 0xff0, 0xfd1, 0xfb3, 0xf96, 0xf79, 0xf5d, 0xf42, 0xf28, 0xf0e, 0xef5, 0xedd, 0xec5, 0xeae, 0xe97, 0xe81, 0xe6c, 0xe57, 0xe43, 0xe2f, 0xe1c, 0xe09, 0xdf7, 0xde5, 0xdd3, 0xdc2, 0xdb1, 0xda1, 0xd91, 0xd81, 0xd72, 0xd63, 0xd54, 0xd46, 0xd38, 0xd2a, 0xd1d, 0xd0f, 0xd02, 0xcf5, 0xce9, 0xcdc, 0xcd0, 0xcc4, 0xcb8, 0xcac, 0xca1, 0xc96, 0xc8b, 0xc80, 0xc75, 0xc6b, 0xc60, 0xc56, 0xc4c, 0xc42, 0xc38, 0xc2f, 0xc25, 0xc1c, 0xc12, 0xc09, 0xc00, 0xbf7, 0xbee, 0xbe6, 0xbdd, 0xbd5, 0xbcc, 0xbc4, 0xbbc, 0xbb3, 0xbab, 0xba3, 0xb9c, 0xb94, 0xb8c, 0xb85, 0xb7d, 0xb76, 0xb6e, 0xb67, 0xb60, 0xb59, 0xb52, 0xb4b, 0xb44, 0xb3d, 0xb37, 0xb30, 0xb2a, 0xb23, 0xb1d, 0xb17, 0xb10, 0xb0a, 0xb04, 0xafe, 0xaf8, 0xaf2, 0xaec, 0xae7, 0xae1, 0xadb, 0xad6, 0xad0, 0xacb, 0xac5, 0xac0, 0xabb, 0xab5, 0xab0, 0xaab, 0xaa6, 0xaa1, 0xa9c, 0xa97, 0xa92, 0xa8d, 0xa88, 0xa84, 0xa7f, 0xa7a, 0xa76, 0xa71, 0xa6d, 0xa68, 0xa64, 0xa5f, 0xa5b, 0xa56, 0xa52, 0xa4e, 0xa4a, 0xa45, 0xa41, 0xa3d, 0xa39, 0xa35, 0xa31, 0xa2d, 0xa29, 0xa25, 0xa21, 0xa1e, 0xa1a, 0xa16, 0xa12, 0xa0f, 0xa0b, 0xa07, 0xa04, 0xa00, 0x9fc, 0x9f9, 0x9f5, 0x9f2, 0x9ee, 0x9eb, 0x9e7, 0x9e4, 0x9e0, 0x9dd, 0x9d9, 0x9d6, 0x9d3, 0x9cf, 0x9cc, 0x9c9, 0x9c5, 0x9c2, 0x9bf, 0x9bc, 0x9b8, 0x9b5, 0x9b2, 0x9af, 0x9ac, 0x9a9, 0x9a6, 0x9a3, 0x9a0, 0x99d, 0x99a, 0x997, 0x994, 0x991, 0x98e, 0x98b, 0x988, 0x985, 0x982, 0x97f, 0x97c, 0x979, 0x977, 0x974, 0x971, 0x96e, 0x96c, 0x969, 0x966, 0x963, 0x961, 0x95e, 0x95b, 0x959, 0x956, 0x953, 0x951, 0x94e, 0x94c, 0x949, 0x946, 0x944, 0x941, 0x93f, 0x93c, 0x93a, 0x937, 0x935, 0x932, 0x930, 0x92d, 0x92b, 0x928, 0x926, 0x924, 0x921, 0x91f, 0x91d, 0x91a, 0x918, 0x916, 0x913, 0x911, 0x90f, 0x90c, 0x90a, 0x908, 0x906, 0x903, 0x901, 0x8ff, 0x8fd, 0x8fa, 0x8f8, 0x8f6, 0x8f4, 0x8f2, 0x8ef, 0x8ed, 0x8eb, 0x8e9, 0x8e7, 0x8e5, 0x8e3, }; // 结果的尾数:查表得到的高8位,后面补15个0,凑齐23位标准尾数 uint32_t res_frac = recp_table[table_idx] << 15; // 组合成结果的二进制位 uint32_t res_bits = (sign << 31) | ((uint32_t)res_exp << 23) | res_frac; float res; memcpy(&res, &res_bits, sizeof(res)); return res; }
如果需要处理4路向量,对每个元素单独调用上述函数即可,输出结果和你测试得到的0.99805, 0.49902, 0.33301, 0.24951完全一致。
为什么1的倒数结果是0.99805而非1.0
vrecpeq_f32本身就是低精度快速近似指令,设计目标优先保证运算速度而非精度,它的精度固定为8位,最大相对误差在1/256左右,不会对1这类特殊值做额外的精确匹配处理。
对于输入1,它的指数为127、尾数为0,查表得到的近似尾数对应的数值就是接近1但略小的0.99805,符合该指令的设计预期。
内容的提问来源于stack exchange,提问作者ChrisZZ
相关产品推荐
相关产品推荐

