如何改变double的字节顺序?
How to change double's endianness?
我需要从串口读取数据。它们是小字节序的,但我需要独立于平台进行处理,所以我必须涵盖 double
的字节序。我找不到任何地方,如何去做,所以我写了自己的函数。但我不确定。 (而且我没有大端的机器来试一试)。
这能正常工作吗?还是我找不到更好的方法?
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。我用 double
8 个字节长来计算,所以请不要为这个烦恼。我知道这可能有问题
编辑:在建议我应该使用 union 之后,我这样做了:
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;
}
更好? (虽然我看不出有什么区别)
EDIT2:尝试#3,你现在怎么看?
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:我用gcc -std=gnu99 -lpthread -Wall -pedantic
编译它
Edit4:在下一个建议之后,我添加了一个字节顺序检查条件。老实说,我不知道我现在在做什么(不应该有类似 __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;
}
我只是去手动将字节复制到临时双精度,然后 return。在 C(我认为是 C++)中,可以将任何指针转换为 char *
;正如我在其中一条评论中提到的,这是对别名的明确许可之一(在标准草案 n1570 中,第 6.5/7 段)。为了完成任何接近硬件的事情,异常是绝对必要的;包括通过网络接收的反向字节:-)。
遗憾的是,没有标准的编译时方法来确定是否有必要;如果你想避免分支,如果你处理大量数据,这可能是个好主意,你应该查看你的编译器文档以获取专有定义,以便你可以在编译时选择正确的代码分支。 gcc, for example, 已将 __FLOAT_WORD_ORDER__
设置为 __ORDER_LITTLE_ENDIAN__
或 __ORDER_BIG_ENDIAN__
。
(因为您在评论中提出的问题:__FLOAT_WORD_ORDER__
通常表示浮点数。设计一个针对不同数据大小具有不同字节顺序的 FPU 将是一个非常变态的头脑 :-)。实际上,没有多少主流架构对浮点类型和整数类型具有不同的字节顺序。正如维基百科所说,小系统可能会有所不同。)
Basile 指向 ntohd
,一个在 Windows 上存在但显然不在 Linux 上的转换函数。
我天真的示例实现就像
/** 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
}
这里有一个工作示例:https://ideone.com/aV9mj4。
一个改进的版本将迎合给定的 CPU -- 它可能有一个 8 字节的交换命令。
如果您可以在两侧(发射器和接收器)调整软件,您可以使用一些 serialization library and format. It could be using old XDR routines like xdr_double
in your case (see xdr(3)...). You could also consider ASN1 or using textual formats like JSON。
XDR 是 big endian. You might try to find some NDR 实现,是小端。
另请参阅 this 相关问题,以及 htond
的 STFW
好的,最后,感谢大家,我找到了最好的解决方案。现在我的代码如下所示:
double reverseDouble(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;
}
double get_double(uint8_t * buff){
double value;
memcpy(&value,buff,sizeof(double));
if (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
return reverseDouble((char *)&value);
else
return value;
}
p.s。 (检查定义等在其他地方)
如果您可以假设双精度的字节顺序与整数相同(通常您不能这样做,但几乎总是相同),您可以通过逐字节移动它来将其读取为整数,然后将表示转换为双精度。
double get_double_from_little_endian(uint8_t * buff) {
uint64_t u64 = ((uint64_t)buff[0] << 0 |
(uint64_t)buff[1] << 8 |
(uint64_t)buff[2] << 16 |
(uint64_t)buff[3] << 24 |
(uint64_t)buff[4] << 32 |
(uint64_t)buff[5] << 40 |
(uint64_t)buff[6] << 48 |
(uint64_t)buff[7] << 56);
return *(double *)(char *)&u64;
}
这只是标准 C,编译器对其进行了很好的优化。尽管 C 标准没有定义浮点数在内存中的表示方式,因此像 __FLOAT_WORD_ORDER__
这样依赖于编译器的宏可能会提供更好的结果,但是如果您还想涵盖“mixed-endian IEEE 格式”,它会变得更加复杂" 在 ARM old-ABI 上找到。
我需要从串口读取数据。它们是小字节序的,但我需要独立于平台进行处理,所以我必须涵盖 double
的字节序。我找不到任何地方,如何去做,所以我写了自己的函数。但我不确定。 (而且我没有大端的机器来试一试)。
这能正常工作吗?还是我找不到更好的方法?
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。我用 double
8 个字节长来计算,所以请不要为这个烦恼。我知道这可能有问题
编辑:在建议我应该使用 union 之后,我这样做了:
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;
}
更好? (虽然我看不出有什么区别)
EDIT2:尝试#3,你现在怎么看?
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:我用gcc -std=gnu99 -lpthread -Wall -pedantic
Edit4:在下一个建议之后,我添加了一个字节顺序检查条件。老实说,我不知道我现在在做什么(不应该有类似 __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;
}
我只是去手动将字节复制到临时双精度,然后 return。在 C(我认为是 C++)中,可以将任何指针转换为 char *
;正如我在其中一条评论中提到的,这是对别名的明确许可之一(在标准草案 n1570 中,第 6.5/7 段)。为了完成任何接近硬件的事情,异常是绝对必要的;包括通过网络接收的反向字节:-)。
遗憾的是,没有标准的编译时方法来确定是否有必要;如果你想避免分支,如果你处理大量数据,这可能是个好主意,你应该查看你的编译器文档以获取专有定义,以便你可以在编译时选择正确的代码分支。 gcc, for example, 已将 __FLOAT_WORD_ORDER__
设置为 __ORDER_LITTLE_ENDIAN__
或 __ORDER_BIG_ENDIAN__
。
(因为您在评论中提出的问题:__FLOAT_WORD_ORDER__
通常表示浮点数。设计一个针对不同数据大小具有不同字节顺序的 FPU 将是一个非常变态的头脑 :-)。实际上,没有多少主流架构对浮点类型和整数类型具有不同的字节顺序。正如维基百科所说,小系统可能会有所不同。)
Basile 指向 ntohd
,一个在 Windows 上存在但显然不在 Linux 上的转换函数。
我天真的示例实现就像
/** 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
}
这里有一个工作示例:https://ideone.com/aV9mj4。
一个改进的版本将迎合给定的 CPU -- 它可能有一个 8 字节的交换命令。
如果您可以在两侧(发射器和接收器)调整软件,您可以使用一些 serialization library and format. It could be using old XDR routines like xdr_double
in your case (see xdr(3)...). You could also consider ASN1 or using textual formats like JSON。
XDR 是 big endian. You might try to find some NDR 实现,是小端。
另请参阅 this 相关问题,以及 htond
好的,最后,感谢大家,我找到了最好的解决方案。现在我的代码如下所示:
double reverseDouble(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;
}
double get_double(uint8_t * buff){
double value;
memcpy(&value,buff,sizeof(double));
if (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
return reverseDouble((char *)&value);
else
return value;
}
p.s。 (检查定义等在其他地方)
如果您可以假设双精度的字节顺序与整数相同(通常您不能这样做,但几乎总是相同),您可以通过逐字节移动它来将其读取为整数,然后将表示转换为双精度。
double get_double_from_little_endian(uint8_t * buff) {
uint64_t u64 = ((uint64_t)buff[0] << 0 |
(uint64_t)buff[1] << 8 |
(uint64_t)buff[2] << 16 |
(uint64_t)buff[3] << 24 |
(uint64_t)buff[4] << 32 |
(uint64_t)buff[5] << 40 |
(uint64_t)buff[6] << 48 |
(uint64_t)buff[7] << 56);
return *(double *)(char *)&u64;
}
这只是标准 C,编译器对其进行了很好的优化。尽管 C 标准没有定义浮点数在内存中的表示方式,因此像 __FLOAT_WORD_ORDER__
这样依赖于编译器的宏可能会提供更好的结果,但是如果您还想涵盖“mixed-endian IEEE 格式”,它会变得更加复杂" 在 ARM old-ABI 上找到。