2

I have a requirement where I need to read the 4 raw bytes of the single precision IEEE754 floating point representation as to send on the serial port as it is without any modification. I just wanted to ask what is the correct way of extracting the bytes among the following:

1.) creating a union such as:

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

and then just reading the bytes[] array after writing to the float variable f?

2.) Or, extracting bytes by a function in which a uint32_t type pointer is made to point to the float variable and then the bytes are extracted via masking

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

where calling will be made like:

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

Please suggest me the correct way if you think both the above-mentioned methods are wrong!

Akay
  • 1,092
  • 12
  • 32
  • Your second method violates the strict aliasing rule. You could us a char pointer instead. – Support Ukraine Jul 31 '18 at 07:39
  • 2
    You say that you want to extract 4 bytes so it's unclear to me why you operate at bit level. Please clarify. – Support Ukraine Jul 31 '18 at 07:42
  • Actually, that I just put a generic function to extract any bit /word location from the float? – Akay Jul 31 '18 at 07:43
  • 1
    Beware of endianness. Your methods could not return bytes in the same order. Anyway the standard enforces the use of a character pointer to access the bytes of the representation of any type. – Serge Ballesta Jul 31 '18 at 07:44
  • And using bit fields in an union only guarantees that you cannot know how an implementation will map the bits... Please don't. – Serge Ballesta Jul 31 '18 at 07:45
  • 1
    If endianness is important for your use case, have a look at this question : https://stackoverflow.com/questions/10620601/portable-serialisation-of-ieee754-floating-point-values – Sander De Dycker Jul 31 '18 at 08:17

3 Answers3

3

First of all, I assume you're coding specifically for a platform where float actually is represented in a IEEE754 single. You can't take this for granted in general, so your code won't be portable to all platforms.

Then, the union approach is the correct one. But don't add this bitfield member! There's no guarantee how the bits will be arranged, so you might access the wrong bits. Just do this:

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

Also, don't add a _t suffix to your own types. On POSIX systems, this is reserved to the implementation, so it's best to always avoid it.

Instead of using a union, accessing the bytes through a char pointer is fine as well:

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

Note in both cases, you are accessing the representation in memory, this means you have to pay attention to the endianness of your machine.


Your second option isn't correct, it violates the strict aliasing rule. In short, you're not allowed to access an object through a pointer that doesn't have compatible type, a char pointer is an explicit exception for accessing the representation. The exact rules are written in 6.5 p7 of N1570, the latest draft to the C11 standard.

  • is there a way to command the compiler to arrange the bits in a specific order? just curious! – Akay Jul 31 '18 at 08:41
  • 3
    No. Of course, a compiler could provide some extension, but that's not covered by the standard. The use-case for bitfields is to conserve space, so they are *mostly* useless nowadays. –  Jul 31 '18 at 08:45
  • How do you suggest should I reframe the function extractBitsFloat() if I have to do bitmasking and extracting bit/bytes at certain locations in the 32-bit float type, without violating the strict aliasing rule? Because after creating the mask, I have to AND it with the 32-bit number itself, how can I do this when using char pointer for accessing the float representation? – Akay Jul 31 '18 at 09:49
  • 2
    @Akay you can for example `memcpy()` to an `uint32_t`. –  Jul 31 '18 at 11:05
2

You can do:

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

and then read 4 bytes from where p is pointing (e.g. p[0], p[1], etc.). The exact best code to "read 4 bytes" depends on what form the serial port function accepts data in.

M.M
  • 138,810
  • 21
  • 208
  • 365
1

If you do not care of endianness, just alias a character pointer to the address of a float. The standard explicitely allows to use a charater pointer to access the bytes of the representation of any type. If you need a specific endianness to send the bytes on the serial port, you can test for it before sending:

  1. Simple way, just use native endianness:

    float f;
    ...
    char * bytes = &f;       // bytes point the the beginning of a char array of size sizeof(f)
    
  2. Automatically test for endianness and uses big endian (AKA network order). The struct is just a trick to return an array and have thread safe code.

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

    Beware: the test for endianness will only make sense if floating point is IEEE754 single.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252