【问题标题】:Building a 32-bit float out of its 4 composite bytes用 4 个复合字节构建一个 32 位浮点数
【发布时间】:2011-04-28 20:02:26
【问题描述】:

我正在尝试用它的 4 个复合字节构建一个 32 位浮点数。有没有比使用以下方法更好(或更便携)的方法?

#include <iostream>

typedef unsigned char uchar;

float bytesToFloat(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b0;
    *((uchar*)(&output) + 2) = b1;
    *((uchar*)(&output) + 1) = b2;
    *((uchar*)(&output) + 0) = b3;

    return output;
}

int main()
{
    std::cout << bytesToFloat(0x3e, 0xaa, 0xaa, 0xab) << std::endl; // 1.0 / 3.0
    std::cout << bytesToFloat(0x7f, 0x7f, 0xff, 0xff) << std::endl; // 3.4028234 × 10^38  (max single precision)

    return 0;
}

【问题讨论】:

  • 考虑到这是我在 Stack Overflow 上的第一个问题,我对各种回复感到非常兴奋。我感谢大家的意见。

标签: c++ floating-point endianness portability single-precision


【解决方案1】:

您可以使用memcpy (Result)

float f;
uchar b[] = {b3, b2, b1, b0};
memcpy(&f, &b, sizeof(f));
return f;

或联合* (Result)

union {
  float f;
  uchar b[4];
} u;
u.b[3] = b0;
u.b[2] = b1;
u.b[1] = b2;
u.b[0] = b3;
return u.f;

但这并不比你的代码更便携,因为不能保证平台是 little-endian 或 float 使用 IEEE binary32 甚至 sizeof(float) == 4

(注*:正如@James 所解释的,在标准(C++ §[class.union]/1)中技术上不允许访问联合成员u.f。)

【讨论】:

  • 要解决sizeof(float) 问题,您只需将b 成员声明为uchar b[sizeof(float)];
  • @Matteo:是的,但是输入也需要修改。
【解决方案2】:

你可以使用std::copy:

float bytesToFloat(uchar b0, uchar b1, uchar b2, uchar b3) 
{ 
    uchar byte_array[] = { b3, b2, b1, b0 };
    float result;
    std::copy(reinterpret_cast<const char*>(&byte_array[0]),
              reinterpret_cast<const char*>(&byte_array[4]),
              reinterpret_cast<char*>(&result));
    return result;
} 

这避免了 union hack,这在语言技术上是不允许的。它还避免了常用的reinterpret_cast&lt;float*&gt;(byte_array),它违反了严格的别名规则(允许将任何对象重新解释为char 的数组,因此该解决方案中的reinterpret_casts 不违反严格的别名规则)。

它仍然依赖于float 的宽度为四个字节,并且依赖于您的四个字节是实现的浮点格式中的有效浮点数,但是您要么必须做出这些假设,要么必须编写特殊的处理代码进行转换。

【讨论】:

  • @JoshD:不;它仍然依赖sizeof(float) == 4 并且不考虑字节序。它只是避免了reinterpret_cast&lt;float*&gt;(some_uchar_array) 和联合黑客。
  • 我很确定如果 byte_array (1) 正确对齐并且 (2) 实际上包含一个浮点数,则必须允许 reinterpret_cast&lt;float*&gt;(byte_array)。我是这么认为的,因为否则不可能将memcpyfloat 与另一个float(因为memcpy 写入字节数组),而float 是典型的POD 类型。
  • @MSalters:但memcpy 不会将字节数组重新解释为浮点数;它将浮点数重新解释为字节数组。
  • 确实不是memcpy本身;这显然只适用于字节数组。这是保证您可以将输出字节数组用作浮点数。
【解决方案3】:

如果您想要一种可移植的方式来执行此操作,则必须编写一些代码来检测系统的字节序。

float bytesToFloatA(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b0;
    *((uchar*)(&output) + 2) = b1;
    *((uchar*)(&output) + 1) = b2;
    *((uchar*)(&output) + 0) = b3;

    return output;
}


float bytesToFloatB(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b3;
    *((uchar*)(&output) + 2) = b2;
    *((uchar*)(&output) + 1) = b1;
    *((uchar*)(&output) + 0) = b0;

    return output;
}

float (*correctFunction)(uchar b0, uchar b1, uchar b2, uchar b3) = bytesToFloatA;

if ((*correctFunction)(0x3e, 0xaa, 0xaa, 0xab) != 1.f/3.f) // horrifying, I know
{
  correctFunction = bytesToFloatB;
}

【讨论】:

  • 这在任何字节序中都不相等,因为1./3.double,而不是float。你应该使用类似1.0f/3的东西。
【解决方案4】:

没有办法做到这一点,因为不同的平台可以使用:

我也想知道你从哪里得到这 4 个字节?

如果我假设您从另一个系统获取它们,并且您可以保证两个系统使用完全相同的方法将浮点值存储在内存中,那么您可以使用联合技巧。否则,您的代码几乎可以保证是不可移植的。

【讨论】:

    【解决方案5】:

    以下函数按网络字节顺序将表示单精度浮点值的字节打包/解包到缓冲区。只有 pack 方法需要考虑字节序,因为 unpack 方法通过将它们移位适当的数量然后将它们组合在一起,从各个字节显式构造 32 位值。这些函数仅对以 32 位存储浮点数的 C/C++ 实现有效。 IEEE 754-1985 浮点实现也是如此。

    // unpack method for retrieving data in network byte,
    //   big endian, order (MSB first)
    // increments index i by the number of bytes unpacked
    // usage:
    //   int i = 0;
    //   float x = unpackFloat(&buffer[i], &i);
    //   float y = unpackFloat(&buffer[i], &i);
    //   float z = unpackFloat(&buffer[i], &i);
    float unpackFloat(const void *buf, int *i) {
        const unsigned char *b = (const unsigned char *)buf;
        uint32_t temp = 0;
        *i += 4;
        temp = ((b[0] << 24) |
                (b[1] << 16) |
                (b[2] <<  8) |
                 b[3]);
        return *((float *) &temp);
    }
    
    // pack method for storing data in network,
    //   big endian, byte order (MSB first)
    // returns number of bytes packed
    // usage:
    //   float x, y, z;
    //   int i = 0;
    //   i += packFloat(&buffer[i], x);
    //   i += packFloat(&buffer[i], y);
    //   i += packFloat(&buffer[i], z);
    int packFloat(void *buf, float x) {
        unsigned char *b = (unsigned char *)buf;
        unsigned char *p = (unsigned char *) &x;
    #if defined (_M_IX86) || (defined (CPU_FAMILY) && (CPU_FAMILY == I80X86))
        b[0] = p[3];
        b[1] = p[2];
        b[2] = p[1];
        b[3] = p[0];
    #else
        b[0] = p[0];
        b[1] = p[1];
        b[2] = p[2];
        b[3] = p[3];
    #endif
        return 4;
    }
    

    【讨论】:

    • 我认为代码行有错误:return *((float *) temp);应该是:return *((float *) &temp);
    • 由于严格的别名规则,我很确定这是 C++ 中未定义的行为。
    【解决方案6】:

    我通常在 C 中使用它——不需要 memcpyunion。它可能会破坏 C++ 中的别名规则,我不知道。

    float bytesToFloat(uint8_t *bytes, bool big_endian) {
        float f;
        uint8_t *f_ptr = (uint8_t *) &f;
        if (big_endian) {
            f_ptr[3] = bytes[0];
            f_ptr[2] = bytes[1];
            f_ptr[1] = bytes[2];
            f_ptr[0] = bytes[3];
        } else {
            f_ptr[3] = bytes[3];
            f_ptr[2] = bytes[2];
            f_ptr[1] = bytes[1];
            f_ptr[0] = bytes[0];
        }
        return f;
    }
    

    如果您有一个完整的字节数组需要重新解释为浮点数,您可以在必要时为数组中每个连续的 4 个字节序列调用以下过程,以切换字节顺序(例如,如果您是在小端机器上运行,但字节按大端顺序排列)。然后您可以简单地将uint8_t * 数组指针转换为float *,并以浮点数组的形式访问内存。

    void switchEndianness(uint8_t *bytes) {
        uint8_t b0 = bytes[0];
        uint8_t b1 = bytes[1];
        uint8_t b2 = bytes[2];
        uint8_t b3 = bytes[3];
        bytes[0] = b3;
        bytes[1] = b2;
        bytes[2] = b1;
        bytes[3] = b0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-27
      • 1970-01-01
      • 2023-03-24
      • 2021-01-19
      • 1970-01-01
      • 2016-06-01
      • 2019-11-16
      • 1970-01-01
      相关资源
      最近更新 更多