How to byteswap a double?

隐身守侯 提交于 2020-01-12 22:39:32

问题


I'm trying to write a byteswap routine for a C++ program running on Win XP. I'm compiling with Visual Studio 2008. This is what I've come up with:

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;
}

And a function to test:

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);
        }
    }
}

Getting these numbers not matching:

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

This is after having read through:
How do I convert between big-endian and little-endian values in C++?
Little Endian - Big Endian Problem
You can't use << and >> on double, by the way (unless I'm mistaken?)


回答1:


Although a double in main memory is 64 bits, on x86 CPUs double-precision registers are 80 bits wide. So if one of your values is stored in a register throughout, but the other makes a round-trip through main memory and is truncated to 64 bits, this could explain the small differences you're seeing.

Maybe you can force variables to live in main memory by taking their address (and printing it, to prevent the compiler from optimizing it out), but I'm not certain that this is guaranteed to work.




回答2:


    b = byteswap(a);

That's a problem. After swapping the bytes, the value is no longer a proper double. Storing it back to a double is going to cause subtle problems when the FPU normalizes the value. You have to store it back into an __int64 (long long). Modify the return type of the method.




回答3:


Try 3

Okay, found out there's a better way. The other way you have to worry about the order you pack/unpack stuff. This way you don't:

// 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);
}    

Send:

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

Recv:

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



回答4:


Try 2

Okay, got it working! Hans Passant was right. They got me thinking with the "no longer a proper double" comment. So you can't byteswap a float into another float because then it might be in an improper format, so you have to byteswap to a char array and unswap back. This is the code I used:

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;
}

And a simple test function:

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);
}


来源:https://stackoverflow.com/questions/4949144/how-to-byteswap-a-double

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!