Home > other >  Can you reverse the byte order of a double and store the result back in a double in C?
Can you reverse the byte order of a double and store the result back in a double in C?

Time:01-17

I'm currently writing a binary application protocol in C that sends uint32_t and doubles across the network using sockets. When writing uint32_t's, I always use the htonl() functions to convert the host byte order to network byte order. However, such a function does not exist for doubles. So I ended up writing a new function which checks the host endianness and reverses the byte order of the double if necessary.

double netHostToNetFloat64(double input)
{
    typedef union DoubleData
    {
        double d;
        char c[8];
    } DoubleData;

    DoubleData input_data, output_data;
    
    if(netHostIsBigEndian())
    {
        return input;
    }
    else
    {
        input_data.d = input;
        output_data.c[0] = input_data.c[7];
        output_data.c[1] = input_data.c[6];
        output_data.c[2] = input_data.c[5];
        output_data.c[3] = input_data.c[4];
        output_data.c[4] = input_data.c[3];
        output_data.c[5] = input_data.c[2];
        output_data.c[6] = input_data.c[1];
        output_data.c[7] = input_data.c[0];
        return output_data.d;
    }
}

However, I'm getting some weird results when the network peer reads the double. I'm confident that my function is reversing the byte order, however, I'm curious if storing an invalid double value (i.e. it results in an Inf or NaN) corrupts the output the next time it is read?

Also, I realize that reversing the byte order and placing the result back into a double is stupid, however, I wanted to keep it consistent with the htonl/htons/ntohl/ntohs functions.

CodePudding user response:

I would use other way of reversing the value.

uint64_t reversebytes(double d)
{
    uint64_t u;
    memcpy(&u, &d, sizeof(u));

    u = (u >> 56) | (u << 56) |
        ((u & 0x00ff000000000000) >> 40) | ((u & 0x000000000000ff00) << 40) |
        ((u & 0x0000ff0000000000) >> 24) | ((u & 0x0000000000ff0000) << 24) |
        ((u & 0x000000ff00000000) >> 8)  | ((u & 0x00000000ff000000) << 8) ;
    return u;
}

This code is very well optimized by the compilers: https://godbolt.org/z/qn6ar3hvM

Your function is much more difficult for the compiler to optimize: https://godbolt.org/z/M4shPoEnE

CodePudding user response:

Double is eight bytes, so you need to reverse the eight bytes of the value to send it on the network... normally, when you send a IEEE-752, the protocol specification indicates which byte must be sent first (it's normally the most significant byte of the double value, which has the sign and the most significant byte of the exponent of two.) but quite common (in Intel architecture) to store that byte just in the last byte (following something similar to little endian again) so you must change all the bytes order to appear in reverse, before transmitting. Other architectures I'm not sure what they do, but probably will do something similar. You could use this:

uint64_t llhton(void *p)
{
    uint64_t data = *(uint64_t *)p;
    src = (data & 0xffffffff00000000) >> 32 | (data & 0x00000000ffffffff) << 32;
    src = (data & 0xffff0000ffff0000) >> 16 | (data & 0x0000ffff0000ffff) << 16;
    src = (data & 0xff00ff00ff00ff00) >> 8 | (data & 0x00ff00ff00ff00ff) << 8;
    return data;
}

if you have:

    double value;

you can serialize it for output:

    uint64_t data_in_network_order = llhton(&value);

while data_in_network_order is in network order, it has no meaning in this architecture, so I used a 64bit unsigned data for convenience only, it should be considered opaque data.

  •  Tags:  
  • Related