【发布时间】:2016-11-04 06:35:42
【问题描述】:
我一直在努力寻找一种可移植的方法来序列化 C 和 C++ 中的 32 位浮点变量,以便与微控制器之间发送和接收。我希望格式定义得足够好,以便序列化/反序列化也可以从其他语言完成,而无需太多努力。相关问题是:
Portability of binary serialization of double/float type in C++
Serialize double and float with C
c++ portable conversion of long to double
我知道在大多数情况下,typecast union/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 << 31;
}
buffer[(*index)++] = (res >> 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]) << 8 |
((uint32_t) buffer[*index + 3]);
*index += 4;
int e = (res >> 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,但现在这不是必需的。
所以,最后,我的问题是:这看起来是一种合理的方法,还是我只是在制作一个仍然存在可移植性问题的复杂解决方案?
【问题讨论】:
-
简短回答:你不能真正以可移植的方式做到这一点,除非使用反序列化库/工具,例如 google protobuf。
-
那么我提出的方法有什么问题?我通常读到的问题是浮点表示不能保证在所有系统上都是相同的,所以我的尝试旨在生成始终相同的东西,无论浮点的内部表示是什么。
-
Endianess 列举其中一个问题。
-
据我所知,字节移位是安全的:stackoverflow.com/questions/7184789/…我没有使用任何类型转换。
-
Frexp 永远不会返回 1,所以不用担心。
标签: c++ c floating-point embedded