我一直在努力寻找一种可移植的方法来序列化C和C ++中的32位浮点变量,以便发送到微控制器和从微控制器发送。 我希望格式足够明确,以便可以从其他语言完成序列化/反序列化,而无需太多努力。 相关问题是:
C ++中双/浮点型二进制序列化的可移植性
用C序列化double和float
c ++便携式转换为long to double
我知道在大多数情况下, 类型转换联合/ memcpy可以正常工作,因为浮动表示是相同的,但我宁愿有更多的控制和心灵。 到目前为止我想出的是以下内容:
void serialize_float32(uint8_t* buffer, float number, int32_t *index) { int e = 0; float sig = frexpf(number, &e); float sig_abs = fabsf(sig); uint32_t sig_i = 0; if (sig_abs >= 0.5) { sig_i = (uint32_t)((sig_abs - 0.5f) * 2.0f * 8388608.0f); e += 126; } uint32_t res = ((e & 0xFF) << 23) | (sig_i & 0x7FFFFF); if (sig < 0) { res |= 1 <> 24) & 0xFF; buffer[(*index)++] = (res >> 16) & 0xFF; buffer[(*index)++] = (res >> 8) & 0xFF; buffer[(*index)++] = res & 0xFF; }
和
float deserialize_float32(const uint8_t *buffer, int32_t *index) { uint32_t res = ((uint32_t) buffer[*index]) << 24 | ((uint32_t) buffer[*index + 1]) << 16 | ((uint32_t) buffer[*index + 2]) <> 23) & 0xFF; uint32_t sig_i = res & 0x7FFFFF; bool neg = res & (1 << 31); float sig = 0.0; if (e != 0 || sig_i != 0) { sig = (float)sig_i / (8388608.0 * 2.0) + 0.5; e -= 126; } if (neg) { sig = -sig; } return ldexpf(sig, e); }
frexp和ldexp函数似乎是为了这个目的而制作的,但是如果它们不可用,我尝试使用常见的函数手动实现它们:
float frexpf_slow(float f, int *e) { if (f == 0.0) { *e = 0; return 0.0; } *e = ceil(log2f(fabsf(f))); float res = f / powf(2.0, (float)*e); // Make sure that the magnitude stays below 1 so that no overflow occurs // during serialization. This seems to be required after doing some manual // testing. if (res >= 1.0) { res -= 0.5; *e += 1; } if (res <= -1.0) { res += 0.5; *e += 1; } return res; }
和
float ldexpf_slow(float f, int e) { return f * powf(2.0, (float)e); }
我一直在考虑的一件事是使用8388608(2 ^ 23)或8388607(2 ^ 23 – 1)作为乘数。 文档说frexp返回的值小于1,经过一些实验后,似乎8388608给出了实际浮点数的位精确结果,我找不到溢出的任何极端情况。 但是,对于不同的编译器/系统,情况可能并非如此。 如果这可能成为一个问题,一个较小的乘数会降低精度,我也可以。 我知道这不会处理Inf或NaN,但是现在这不是必需的。
所以,最后,我的问题是:这看起来是一种合理的方法,还是我只是制作一个仍然存在可移植性问题的复杂解决方案?
您似乎在serialize_float
有一个错误:最后4行应该是:
buffer[(*index)++] = (res >> 24) & 0xFF; buffer[(*index)++] = (res >> 16) & 0xFF; buffer[(*index)++] = (res >> 8) & 0xFF; buffer[(*index)++] = res & 0xFF;
对于无穷大和/或NaN,您的方法可能无法正常工作,因为偏移量为126
而不是128
。 请注意,您可以通过大量测试对其进行validation:只有40亿个值,尝试所有可能性不应该花费很长时间。
float
值在存储器中的实际表示可能在不同的体系结构上有所不同,但IEEE 854(或更准确地说是IEC 60559)在当今很普遍。 您可以通过检查__STDC_IEC_559__
是否已定义来validation您的特定目标是否符合要求。 但请注意,即使您可以使用IEEE 854,也必须处理系统之间可能存在的不同字节顺序。 您不能假设float
s的endianness与同一平台的整数相同。
另请注意,简单的uint32_t res = *(uint32_t *)&number;
是不正确的: uint32_t res = *(uint32_t *)&number;
违反了严格的别名规则。 您应该使用union
或使用memcpy(&res, &number, sizeof(res));
假设float是IEEE 754格式,提取尾数,指数和符号是完全可移植的:
uint32_t internal; float value = //...some value memcpy( &internal , &value , sizeof( value ) );
const uint32_t sign = ( internal >> 31u ) & 0x1u; const uint32_t mantissa = ( internal >> 0u ) & 0x7FFFFFu; const uint32_t exponent = ( internal >> 23u ) & 0xFFu;
反转过程以构造浮点数。
如果只想发送整个浮点数,那么只需将其复制到缓冲区即可。 即使float不是IEEE 754,这也可以工作,但它必须是32位,并且整数和浮点类型的字节顺序必须相同:
buffer[0] = ( internal >> 0u ) & 0xFFu; buffer[1] = ( internal >> 8u ) & 0xFFu; buffer[2] = ( internal >> 16u ) & 0xFFu; buffer[3] = ( internal >> 24u ) & 0xFFu;
以上就是c/c++开发分享将float序列化为32位整数的便携方式相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/560828.html