I need to read data from serial port. They are in little endian, but I need to do it platform independent, so I have to cover the endianness of double
. I couldn't find anywhere, how to do it, so I wrote my own function. But I am not sure with it. (and I don't have a machine with big endian to try it on).
Will this work correctly? or is there some better approach I wasn't able to find?
double get_double(uint8_t * buff){
double value;
memcpy(&value,buff,sizeof(double));
uint64_t tmp;
tmp = le64toh(*(uint64_t*)&value);
value = *(double*) &tmp;
return value;
}
p.s. I count with double
8 bytes long, so don't bother with this pls. I know that there might be problems with this
EDIT: After suggestion, that I should use union, I did this:
union double_int{
double d;
uint64_t i;
};
double get_double(uint8_t * buff){
union double_int value;
memcpy(&value,buff,sizeof(double));
value.i = le64toh(value.i);
return value.d;
}
better? (though I don't see much of a difference)
EDIT2: attemtp #3, what do you think now?
double get_double(uint8_t * buff){
double value;
uint64_t tmp;
memcpy(&tmp,buff,sizeof(double));
tmp = le64toh(tmp);
memcpy(&value,&tmp,sizeof(double));
return value;
}
Edit3: I compile it with gcc -std=gnu99 -lpthread -Wall -pedantic
Edit4: After next suggestion I added a condition for endianness order checking. I honestly have no idea, what I am doing right now (shouldn't there be something like __DOUBLE_WORD_ORDER__
?)
double get_double(uint8_t * buff){
double value;
if (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__){
uint64_t tmp;
memcpy(&tmp,buff,sizeof(double));
tmp = le64toh(tmp);
memcpy(&value,&tmp,sizeof(double));
}
else {
memcpy(&value,buff,sizeof(double));
}
return value;
}
I'd just go and copy the bytes manually to a temporary double and then return that. In C (and I think C++) it is fine to cast any pointer to char *
; that's one of the explicit permissions for aliasing (in the standard draft n1570, par. 6.5/7), as I mentioned in one of my comments. The exception is absolutely necessary in order to get anything done that is close to hardware; including reversing bytes received over a network :-).
There is no standard compile time way to determine whether that's necessary which is a pity; if you want to avoid branches which is probably a good idea if you deal with lots of data, you should look up your compiler's documentation for proprietary defines so that you can choose the proper code branch at compile time. gcc, for example, has __FLOAT_WORD_ORDER__
set to either __ORDER_LITTLE_ENDIAN__
or __ORDER_BIG_ENDIAN__
.
(Because of your question in comments: __FLOAT_WORD_ORDER__
means floating points in general. It would be a very sick mind who designs a FPU that has different byte orders for different data sizes :-). In all reality there aren't many mainstream architectures which have different byte orders for floating point vs. integer types. As Wikipedia says, small systems may differ.)
Basile pointed to ntohd
, a conversion function which exists on Windows but apparently not on Linux.
My naive sample implementation would be like
/** copy the bytes at data into a double, reversing the
byte order, and return that.
*/
double reverseValue(const char *data)
{
double result;
char *dest = (char *)&result;
for(int i=0; i<sizeof(double); i++)
{
dest[i] = data[sizeof(double)-i-1];
}
return result;
}
/** Adjust the byte order from network to host.
On a big endian machine this is a NOP.
*/
double ntohd(double src)
{
# if !defined(__FLOAT_WORD_ORDER__) \
|| !defined(__ORDER_LITTLE_ENDIAN__)
# error "oops: unknown byte order"
# endif
# if __FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__
return reverseValue((char *)&src);
# else
return src;
# endif
}
There is a working example here: https://ideone.com/aV9mj4.
An improved version would cater to the given CPU -- it may have an 8 byte swap command.