将float序列化为32位整数的便携方式

Ben*_*der 7 c c++ embedded floating-point

我一直在努力寻找一种可移植的方法来序列化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 << 31;
    }

    buffer[(*index)++] = (res >> 24) & 0xFF;
    buffer[(*index)++] = (res >> 16) & 0xFF;
    buffer[(*index)++] = (res >> 8) & 0xFF;
    buffer[(*index)++] = res & 0xFF;
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

frexpldexp功能似乎为此而作出的,但如果他们不提供我试图执行这些手动以及使用常见的功能:

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;
}
Run Code Online (Sandbox Code Playgroud)

float ldexpf_slow(float f, int e) {
    return f * powf(2.0, (float)e);
}
Run Code Online (Sandbox Code Playgroud)

我一直在考虑的一件事是使用8388608(2 ^ 23)或8388607(2 ^ 23 - 1)作为乘数.文档说frexp返回的值大小小于1,经过一些实验后,似乎8388608给出了实际浮点数的位精确结果,我找不到溢出的任何极端情况.但是,对于不同的编译器/系统,情况可能并非如此.如果这可能成为一个问题,一个较小的乘数会降低精度,我也可以.我知道这不会处理Inf或NaN,但是现在这不是必需的.

所以,最后,我的问题是:这看起来是一种合理的方法,还是我只是制作一个仍然存在可移植性问题的复杂解决方案?

250*_*501 7

假设浮点数为IEEE 754格式,则提取尾数,指数和符号是完全可移植的:

uint32_t internal;
float value = //...some value
memcpy( &internal , &value , sizeof( value ) );
Run Code Online (Sandbox Code Playgroud)

const uint32_t sign =     ( internal >> 31u ) & 0x1u;
const uint32_t mantissa = ( internal >> 0u  ) & 0x7FFFFFu;
const uint32_t exponent = ( internal >> 23u ) & 0xFFu;
Run Code Online (Sandbox Code Playgroud)

反转过程以构造浮点数。

如果只想发送整个float,则只需将其复制到缓冲区即可。即使float不是IEEE 754,它也将起作用,但是它必须是32位,并且整数和浮点类型的字节序必须相同:

buffer[0] = ( internal >> 0u  ) & 0xFFu;
buffer[1] = ( internal >> 8u  ) & 0xFFu;
buffer[2] = ( internal >> 16u ) & 0xFFu;
buffer[3] = ( internal >> 24u ) & 0xFFu;
Run Code Online (Sandbox Code Playgroud)


chq*_*lie 5

您似乎有一个错误serialize_float:最后4行应显示为:

buffer[(*index)++] = (res >> 24) & 0xFF;
buffer[(*index)++] = (res >> 16) & 0xFF;
buffer[(*index)++] = (res >> 8) & 0xFF;
buffer[(*index)++] = res & 0xFF;
Run Code Online (Sandbox Code Playgroud)

您的方法对于无穷大和/或NaN可能无法正常工作,因为存在的偏移量126而不是128。请注意,您可以通过广泛的测试来验证它:只有40亿个值,尝试所有可能性应该不会花费很长时间。

float在不同的体系结构中,值的内存中的实际表示形式可能会有所不同,但是IEEE 854(或更确切地说是IEC 60559)在今天非常普遍。您可以通过检查是否__STDC_IEC_559__已定义特定目标来验证它们是否符合要求。但是请注意,即使可以采用IEEE 854,也必须处理系统之间可能不同的字节序。您不能假设floats 的字节序与同一平台的整数的字节序相同。

还要注意,简单的转换将是不正确的:uint32_t res = *(uint32_t *)&number;违反了严格的别名规则。您应该使用union或使用memcpy(&res, &number, sizeof(res));