从 IEEE754 浮点格式读取字节的正确方法

Correct way of reading bytes from IEEE754 floating point format

我有一个要求,我需要读取 单精度 IEEE754 浮点 表示的 4 个原始字节,以便在没有任何修改的情况下在串行端口上发送。我只是想问一下提取以下字节的正确方法是什么:

1.) 创建联合,例如:

typedef union {
  float f;
  uint8_t bytes[4];
  struct {
    uint32_t mantissa : 23;
    uint32_t exponent : 8;
    uint32_t sign : 1;
  };
} FloatingPointIEEE754_t ;

然后在写入 float 变量后只读取 bytes[] 数组 f?

2.) 或者,通过函数提取字节,其中 uint32_t 类型指针指向 float 变量,然后通过掩码

提取字节
uint32_t extractBitsFloat(float numToExtFrom, uint8_t numOfBits, uint8_t bitPosStartLSB){
  uint32_t *p = &numToExtFrom;
  /* validate the inputs */
  if ((numOfBits > 32) || (bitPosStartLSB > 31)) return NULL;
  /* build the mask */
  uint32_t mask = ((1 << numOfBits) - 1) << bitPosStartLSB;
  return ((*p & mask) >> bitPosStartLSB);
}

调用方式如下:

valF = -4.235;
byte0 = extractBitsFloat(valF, 8, 0);
byte1 = extractBitsFloat(valF, 8, 8);
byte2 = extractBitsFloat(valF, 8, 16);
byte3 = extractBitsFloat(valF, 8, 24);

如果您认为上述两种方法都不对,请给我正确的方法!

你可以这样做:

unsigned char *p = (unsigned char *)&the_float;

然后从 p 指向的地方读取 4 个字节(例如 p[0]p[1] 等)。 "read 4 bytes" 的确切最佳代码取决于串行端口函数接受数据的形式。

首先,我假设您是专门为一个平台编写代码的,其中 float 实际上 在 IEEE754 single[=38 中表示=].您不能认为这是理所当然的一般,因此您的代码将无法移植到所有平台。

那么,union的做法才是正确的。但是不要添加这个位域成员!无法保证位的排列方式,因此您可能会访问错误的位。只需这样做:

typedef union {
  float f;
  uint8_t bytes[4];
} FloatingPointIEEE754;

此外,不要为您自己的类型添加 _t 后缀。在 POSIX 系统上,这是为实现保留的,因此最好始终避免它。

通过 char 指针访问字节也可以,而不是使用 union

unsigned char *rep = (unsigned char *)&f;
// access rep[0] to rep[3]

请注意,在这两种情况下,您都在访问内存中的表示,这意味着您必须注意机器的endianness


您的第二个选项不正确,它违反了严格的别名规则。简而言之,您不能通过不兼容类型的指针访问对象,char 指针是访问表示的显式例外。确切的规则写在 6.5 p7 of N1570 中,这是 C11 标准的最新草案。

如果您不关心字节顺序,只需将字符指针别名指向浮点数的地址即可。该标准明确允许使用字符指针来访问任何类型表示的字节。如果您需要特定的字节顺序在串口上发送字节,您可以在发送前测试它:

  1. 简单的方法,只需使用本机字节序:

    float f;
    ...
    char * bytes = &f;       // bytes point the the beginning of a char array of size sizeof(f)
    
  2. 自动测试字节顺序并使用大端(又名网络顺序)。 struct 只是 return 数组和线程安全代码的一个技巧。

    struct float_bytes {
        char bytes[sizeof(float)];
    };
    struct float_bytes(float f) {
        float end = 1.;
        float_bytes resul;
        char *src = (char *) &f;
        if (*end == 0) {                // end is 0 on a little endian platform, else 0x3f
            int i = sizeof(f) {         // little endian: reverse the bytes
            while (i > 0) {
                resul.bytes[--i] = src++;
            }
        }
        else {                          // already in big endian order, just memcpy
            memcpy(&(resul.bytes), &f, sizeof(f));
        }
        return resul;
    }
    

    注意:只有当浮点数是 IEEE754 单数时,字节顺序测试才有意义。