【问题标题】:How to byteswap a double?如何对双字节进行字节交换?
【发布时间】:2011-02-09 18:51:39
【问题描述】:

我正在尝试为在 Win XP 上运行的 C++ 程序编写字节交换例程。我正在使用 Visual Studio 2008 进行编译。这是我想出的:

int byteswap(int v) // This is good
{
    return _byteswap_ulong(v);
}

double byteswap(double v) // This doesn't work for some values
{
    union { // This trick is first used in Quake2 source I believe :D
        __int64 i;
        double  d;
    } conv;
    conv.d = v;
    conv.i = _byteswap_uint64(conv.i);
    return conv.d;
}

还有一个要测试的功能:

void testit() {
    double  a, b, c;
    CString str;

    for (a = -100; a < 100; a += 0.01) {
        b = byteswap(a);
        c = byteswap(b);
        if (a != c) {
            str.Format("%15.15f %15.15f %15.15f", a, c, a - c);
        }
    }
}

得到这些不匹配的数字:

-76.789999999988126 -76.790000000017230 0.000000000029104 -30.499999999987718 -30.499999999994994 0.000000000007276 41.790000000014508 41.790000000029060 -0.000000000014552 90.330000000023560 90.330000000052664 -0.000000000029104

这是在阅读之后:
How do I convert between big-endian and little-endian values in C++?
Little Endian - Big Endian Problem
顺便说一句,你不能在 double 上使用 >(除非我弄错了?)

【问题讨论】:

  • 您能否澄清一下 Quake 2 中究竟发明了什么?肯定不是将 double 和 int64 作为同一个联合的字段的想法吗?
  • 抱歉.. 我从 Quake2 中学到了 C,所以我喜欢假装它是最棒的 :D 我现在到处都看到它(联合)。

标签: c++ visual-studio-2008 endianness


【解决方案1】:

虽然主内存中的double 是 64 位,但在 x86 CPU 上,双精度寄存器是 80 位宽。因此,如果您的一个值始终存储在寄存器中,但另一个值在主存储器中往返并被截断为 64 位,这可以解释您所看到的微小差异。

也许您可以通过获取它们的地址(并打印它,以防止编译器对其进行优化)来强制变量驻留在主内存中,但我不确定这是否能保证工作。

【讨论】:

  • +1:我认为这就是这里发生的情况:'a' 保存在一个寄存器中,并且在 'c' 在内存中时在那里递增。
【解决方案2】:
    b = byteswap(a);

这是个问题。交换字节后,该值不再是正确的双精度数。当 FPU 标准化该值时,将其存储回双精度值会导致微妙的问题。您必须将其存储回 __int64 (long long)。修改方法的返回类型。

【讨论】:

  • 这确实有道理!好的,将对此进行调查并回复您
  • @Hans,我认为您在这里有所作为。我在这里的回答中提到了它:stackoverflow.com/a/64056527/4561887。我自己也不是很明白。
【解决方案3】:

试试 3

好的,发现有更好的方法。另一种方式是你必须担心你打包/解包东西的顺序。这样你就不会:

// int and float
static void swap4(void *v)
{
    char    in[4], out[4];
    memcpy(in, v, 4);
    out[0] = in[3];
    out[1] = in[2];
    out[2] = in[1];
    out[3] = in[0];
    memcpy(v, out, 4);
}

// double
static void swap8(void *v)
{
    char    in[8], out[8];
    memcpy(in, v, 8);
    out[0] = in[7];
    out[1] = in[6];
    out[2] = in[5];
    out[3] = in[4];
    out[4] = in[3];
    out[5] = in[2];
    out[6] = in[1];
    out[7] = in[0];
    memcpy(v, out, 8);
}

typedef struct
{
    int theint;
    float   thefloat;
    double  thedouble;
} mystruct;


static void swap_mystruct(void *buf)
{
    mystruct    *ps = (mystruct *) buf;
    swap4(&ps->theint);
    swap4(&ps->thefloat);
    swap8(&ps->thedouble);
}    

发送:

    char    buf[sizeof (mystruct)];
    memcpy(buf, &s, sizeof (mystruct));
    swap_mystruct(buf);

接收:

    mystruct    s;
    swap_mystruct(buf);
    memcpy(&s, buf, sizeof (mystruct));

【讨论】:

  • 摆脱这个:memcpy(in, v, 4); 并直接从v 复制交换到out,然后memcpy 交换值从out 回到v。这为您节省了整个不必要的副本,将整个数组的副本从 3 个减少到 2 个。
  • 还有一个进一步的优化,将整个数组的副本从 2 减少到 1.5:将数组的左半部分复制到临时变量中,将数组的右半部分直接复制到左侧-一半,适当地交换。然后从包含数组旧左半部分的临时变量复制到数组的右半部分,并进行适当的交换。这相当于整个数组的复制操作只有 1.5 次,效率更高。除了一半数组所需的临时变量之外,在原始数组中就地执行所有这些操作。
【解决方案4】:

试试 2

好的,成功了!汉斯·帕桑特是对的。他们让我想到“不再是适当的双重”评论。因此,您不能将一个浮点数字节交换为另一个浮点数,因为它可能格式不正确,因此您必须将字节交换为一个字符数组并取消交换。这是我使用的代码:

int pack(int value, char *buf)
{
    union temp {
        int value;
        char    c[4];
    } in, out;
    in.value = value;
    out.c[0] = in.c[3];
    out.c[1] = in.c[2];
    out.c[2] = in.c[1];
    out.c[3] = in.c[0];
    memcpy(buf, out.c, 4);
    return 4;
}

int pack(float value, char *buf)
{
    union temp {
        float   value;
        char    c[4];
    } in, out;
    in.value = value;
    out.c[0] = in.c[3];
    out.c[1] = in.c[2];
    out.c[2] = in.c[1];
    out.c[3] = in.c[0];
    memcpy(buf, out.c, 4);
    return 4;
}

int pack(double value, char *buf)
{
    union temp {
        double  value;
        char    c[8];
    } in, out;
    in.value = value;
    out.c[0] = in.c[7];
    out.c[1] = in.c[6];
    out.c[2] = in.c[5];
    out.c[3] = in.c[4];
    out.c[4] = in.c[3];
    out.c[5] = in.c[2];
    out.c[6] = in.c[1];
    out.c[7] = in.c[0];
    memcpy(buf, out.c, 8);
    return 8;
}

int unpack(char *buf, int *value)
{
    union temp {
        int value;
        char    c[4];
    } in, out;
    memcpy(in.c, buf, 4);
    out.c[0] = in.c[3];
    out.c[1] = in.c[2];
    out.c[2] = in.c[1];
    out.c[3] = in.c[0];
    memcpy(value, &out.value, 4);
    return 4;
}

int unpack(char *buf, float *value)
{
    union temp {
        float   value;
        char    c[4];
    } in, out;
    memcpy(in.c, buf, 4);
    out.c[0] = in.c[3];
    out.c[1] = in.c[2];
    out.c[2] = in.c[1];
    out.c[3] = in.c[0];
    memcpy(value, &out.value, 4);
    return 4;
}

int unpack(char *buf, double *value)
{
    union temp {
        double  value;
        char    c[8];
    } in, out;
    memcpy(in.c, buf, 8);
    out.c[0] = in.c[7];
    out.c[1] = in.c[6];
    out.c[2] = in.c[5];
    out.c[3] = in.c[4];
    out.c[4] = in.c[3];
    out.c[5] = in.c[2];
    out.c[6] = in.c[1];
    out.c[7] = in.c[0];
    memcpy(value, &out.value, 8);
    return 8;
}

还有一个简单的测试功能:

typedef struct
{
    int theint;
    float   thefloat;
    double  thedouble;
} mystruct;

void PackStruct()
{
    char    buf[sizeof (mystruct)];
    char    *p;
    p = buf;

    mystruct    foo, foo2;
    foo.theint = 1;
    foo.thefloat = 3.14f;
    foo.thedouble = 400.5;

    p += pack(foo.theint, p);
    p += pack(foo.thefloat, p);
    p += pack(foo.thedouble, p);

    // Send or recv char array

    p = buf;
    p += unpack(p, &foo2.theint);
    p += unpack(p, &foo2.thefloat);
    p += unpack(p, &foo2.thedouble);
}

【讨论】:

    【解决方案5】:

    如何在任何数组、变量或任何其他内存块中就地交换字节

    (如int16_tuint16_tuint32_tfloatdouble等):

    这里有一种方法可以将效率从 3 次数组全复制操作提高到 1.5 次数组全复制操作。另见我留下的cmetsunder your answer

    /// \brief          Swap all the bytes in an array to convert from little-endian byte order 
    ///                 to big-endian byte order, or vice versa.
    /// \note           Works for arrays of any size. Swaps the bytes **in place** in the array.
    /// \param[in,out]  byte_array  The array in which to swap the bytes in-place.
    /// \param[in]      len         The length (in bytes) of the array.
    /// \return         None
    void swap_bytes_in_array(uint8_t * byte_array, size_t len) {
        size_t i_left = 0; // index for left side of the array
        size_t i_right = len - 1; // index for right side of the array
        while (i_left < i_right) {
            // swap left and right bytes
            uint8_t left_copy = byte_array[i_left];
            byte_array[i_left] = byte_array[i_right];
            byte_array[i_right] = left_copy;
            i_left++;
            i_right--;
        }
    }
    

    用法:

    double d;
    // Swap the bytes in the double in place
    swap_bytes_in_array((uint8_t*)(&d), sizeof(d));
    
    uint64_t u64;
    // swap the bytes in a uint64_t in place
    swap_bytes_in_array((uint8_t*)(&u64), sizeof(u64));
    

    但请注意,@Hans Passant seems to be onto something here。尽管以上内容在任何有符号或无符号 integer 类型上都可以完美运行,并且似乎也适用于 floatdouble,但它似乎在 long double 上被破坏了。我认为这是因为当我将交换的long double 存储回long double 变量时,如果确定它不再是无效的long double 表示,那么某些东西会自动更改一些交换的字节或其他东西。我不完全确定。

    在许多 64 位系统上,long double 是 16 字节,因此解决方案可能是将 long double 的交换版本保留在 16 字节数组中,而不是尝试使用它或将其转换回long doubleuint8_t 16 字节数组直到 A)它已被发送到接收器(系统的字节顺序相反,所以它现在处于良好状态)和/或 B)字节再次交换回来所以它又是一个有效的long double

    请记住以上内容,以防您也看到 floatdouble 类型的问题,因为我只看到 long double 类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多