如何改变 double 类型的字节序?

4

我需要从串口读取数据,它们是小端的,但我需要实现平台无关性,所以我必须处理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;
}

我计算使用的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;
}

更好了吗?(虽然我没有看出很大的区别)
编辑2:第三次尝试,你现在觉得怎么样?
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;
}

编辑3:我使用gcc -std=gnu99 -lpthread -Wall -pedantic编译它。

编辑4:在下一个建议后,我添加了一个检查字节序的条件。老实说,我现在不知道自己在做什么(难道不应该有像__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;
}

3
你应该使用一个联合。 - Antti Haapala -- Слава Україні
好的,现在,我用联合操作完成了,它会起作用吗? - Charlestone
2
将 double 复制到 uint64_t,然后通过 le64toh uint64_t 进行转换,最后再将其复制回 double。"memcpy 而不是什么?" union - Defter
2
为什么要使用union?将任何指针转换为char指针(然后对内存进行所需操作,例如memcpy)是可以的;这是正式允许的例外情况,以避免常规别名限制,并且C语言没有它就无法工作,任何用C编写的程序也是如此。参见n1570,6.5/7。 - Peter - Reinstate Monica
1
union关键字对于避免memcpy(&tmp,buff,sizeof(double));步骤非常有用,但不能解决“字节序”问题。 - chux - Reinstate Monica
显示剩余11条评论
4个回答

4
我只会手动复制字节到临时 double 中并返回。在 C(和我认为的 C++)中,将任何指针强制转换为 char * 都是可以的;这是别名的明确许可之一(在标准草案 n1570 的第 6.5/7 段中),正如我在其中一个评论中提到的那样。这个例外情况绝对是必要的,以便完成接近硬件的任何任务;包括反转通过网络接收的字节 :-).

没有标准的编译时方法可以确定是否需要这样做,这是一个遗憾;如果你想避免分支,这可能是一个好主意,特别是当你处理大量数据时,你应该查找你的编译器文档中的专有定义,以便在编译时选择适当的代码分支。例如,gcc__FLOAT_WORD_ORDER__ 设置为 __ORDER_LITTLE_ENDIAN____ORDER_BIG_ENDIAN__

(因为您在评论中的问题:__FLOAT_WORD_ORDER__ 意味着浮点数。设计具有不同数据大小的不同字节顺序的FPU的人会是一个非常病态的思维:-) 实际上,没有多少主流架构对浮点和整数类型有不同的字节顺序。正如维基百科所说,小型系统可能会有所不同。)
Basile指出了在Windows上存在但显然在Linux上不存在的转换函数ntohd。
我天真的示例实现将是这样的:
/** 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字节交换命令。

与其使用#error,我宁愿回退到运行时测试。 - edmz
@black 真的。在这个例子中,我很高兴能够有“错误处理” :-) - Peter - Reinstate Monica
注意:在链接的示例中,使用“%le”而不是“%lf”更具说明性。 - chux - Reinstate Monica

1

好的,最终,在你们所有人的帮助下,我找到了最佳解决方案。现在我的代码看起来像这样:

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.(检查定义等在其他地方进行)


1
如果您可以适应软件的两个方面(发射器和接收器),您可以使用一些序列化库和格式。在您的情况下,它可以使用旧的XDR例程,如xdr_double(请参见xdr(3)...)。您还可以考虑使用ASN1或使用文本格式,例如JSON
XDR是大端的。您可以尝试找到一些NDR实现,它是小端的。

还可以查看this相关问题,以及搜索htond


很遗憾,我不能。发射器是一个GPS接收器。 - Charlestone

0
如果您可以假设双精度浮点数的字节序与整数相同(通常情况下不是这样的),则可以通过逐字节移位来读取它,然后将其表示转换为双精度浮点数。
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__,可能会得到更好的结果,但如果您还想涵盖“混合字节序IEEE格式”,则会变得更加复杂,这种格式在ARM旧ABI上被发现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接