如何改变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 上找到。