lundblade
lundblade

Reputation: 121

Efficient conversion of float endianness in strict portable C

Using this answer is good because it is very portable, correct and passes compilers set to be strict, but it is less efficient than I want and than it could be, because it doesn't use the x86 bswap instruction. (Maybe other instruction sets have similar efficient instructions.)

If the system I'm running on supports ntohl() then I would expect ntohl() to use the bswap instruction and gets me close. ntohl() does exactly the right thing, but it only works on uint32_t, not on a float. Casting between a uint32_t and a float is type punning and not allowed by strict compilers. Making a union with a float and a uint32_t runs into undefined compiler behavior (per previous posts here).

My understanding from previous posts is that casting from any pointer type to a char * or vice versa is explicitly allowed. So what is wrong with this solution? I haven't seen it mentioned in any answers yet.

char NetworkOrderFloat[4]; // Assume it contains network-order float bytes

uint32_t HostOrderInt = ntohl(*(uint32_t *)NetworkOrderFloat);

char *Pointer = (char *)&HostOrderInt;

float HostOrderFloat = *(float *)Pointer;

The ideal solution here seems to be more environments supporting ntohf(), but that doesn't seem to have happened yet.

Upvotes: 2

Views: 280

Answers (1)

Pascal Cuoq
Pascal Cuoq

Reputation: 80255

Your proposal breaks “strict aliasing rules”, the first time when it does *(uint32_t *)NetworkOrderFloat. The expression (uint32_t *)NetworkOrderFloat is still the address of an array of chars, and accessing it with an lvalue of type uint32_t is against these rules. Details and more examples can be found in this article.

Using a union to convert a float's representation to uint32_t, on the other hand, is not forbidden by the C standard as far as I know. But you can always use memcpy if you worry that it is.

float NetworkOrderFloat = ...;
uint32_t tmp;
_Static_assert(sizeof(uint32_t)==sizeof(float),"unsupported arch");
memcpy(&tmp, &NetworkOrderFloat, sizeof(float));
tmp = ntohl(tmp);
memcpy(&HostOrderFloat, &tmp, sizeof(float));

A decent modern compiler should compile the memcpy calls to nothing and ntohl to bswap.

Upvotes: 3

Related Questions