c++ - 将 float 序列化为 32 位整数的可移植方法

标签 c++ c floating-point embedded

我一直在努力寻找一种可移植的方法来序列化 C 和 C++ 中的 32 位浮点变量,以便发送到微 Controller 或从微 Controller 发送。我希望格式定义得足够好,以便序列化/反序列化也可以从其他语言完成,而不需要太多努力。相关问题是:

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 会工作得很好,因为 float 表示是相同的,但我更愿意有更多的控制和头脑。到目前为止,我想出了以下内容:

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);
}

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;
}

float ldexpf_slow(float f, int e) {
    return f * powf(2.0, (float)e);
}

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

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

最佳答案

假设 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 的过程。

如果只想发送整个 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;

关于c++ - 将 float 序列化为 32 位整数的可移植方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40416682/

相关文章:

C++ 模板的别名?

c - C 中的段错误,不明白为什么

ruby-on-rails - 如何通过 C 程序的命令行界面编译 C 程序以与我的 ruby​​ gem 一起使用?

c - 当我打开 O_NONBLOCK 时,我得到 "0"和 "I/O error"

c - 分析编号处理代码 : 28% of time in fegetexcept() & optimal compiler flags?

c++ - 如何使用 quazip 压缩目录/文件夹?

c++ - Lambda 函数参数 + 推导改进

c++ - 从带有原始指针的 vector 中删除 std::unique_ptr 的最佳方法?

PHP - 序列化 float

python - Python 是否具有与 java.lang.Math.nextUp 等效的功能?