用C序列化double和float

时间:2010-08-05 19:15:26

标签: c serialization floating-point

如何在C中序列化双打和浮动?

我有以下代码来序列化short,int和chars。

unsigned char * serialize_char(unsigned char *buffer, char value)
{
    buffer[0] = value;
    return buffer + 1;
}

unsigned char * serialize_int(unsigned char *buffer, int value)
{
    buffer[0] = value >> 24;
    buffer[1] = value >> 16;
    buffer[2] = value >> 8;
    buffer[3] = value;
    return buffer + 4;
}

unsigned char * serialize_short(unsigned char *buffer, short value)
{
    buffer[0] = value >> 8;
    buffer[1] = value;
    return buffer + 2;
}

编辑:

我从this question

找到了这些功能

编辑2:

序列化的目的是将数据发送到UDP套接字,并保证即使字节序不同,也可以在其他机器上反序列化。是否有任何其他“最佳实践”来执行此功能,因为我必须序列化整数,双精度数,浮点数和字符*?

8 个答案:

答案 0 :(得分:12)

我记得第一次看到我的例子中使用的演员在“rsqrt”例程的古老的Quake源代码中,包含了我当时看到的最酷的评论(谷歌它,你会喜欢它)< / p>

unsigned char * serialize_float(unsigned char *buffer, float value) 
{ 
    unsigned int ivalue = *((unsigned int*)&value); // warning assumes 32-bit "unsigned int"
    buffer[0] = ivalue >> 24;  
    buffer[1] = ivalue >> 16;  
    buffer[2] = ivalue >> 8;  
    buffer[3] = ivalue;  
    return buffer + 4; 
} 

我希望我能正确理解你的问题(和示例代码)。如果这有用,请告诉我?

答案 1 :(得分:11)

可移植方式:使用frexp序列化(转换为整数尾数和指数)和ldexp进行反序列化。

简单的方法:假设在2010年你关心的任何机器使用IEEE float,声明一个带有float元素和uint32_t元素的联合,并使用整数序列化代码来序列化float。

二进制文件 - 仇恨方式:将所有内容序列化为文本,包含浮点数。使用"%a" printf格式说明符来获取十六进制浮点数,它总是精确表达(假设您不限制"%.4a"之类的精度)并且不会出现舍入错误。您可以使用strtod或任何scanf系列函数来阅读这些内容。

答案 2 :(得分:8)

这会将浮点值打包到intlong long对中,然后您可以使用其他函数进行序列化。 unpack()函数用于反序列化。

这对数字分别代表数字的指数和小数部分。

#define FRAC_MAX 9223372036854775807LL /* 2**63 - 1 */

struct dbl_packed
{
    int exp;
    long long frac;
};

void pack(double x, struct dbl_packed *r)
{
    double xf = fabs(frexp(x, &r->exp)) - 0.5;

    if (xf < 0.0)
    {
        r->frac = 0;
        return;
    }

    r->frac = 1 + (long long)(xf * 2.0 * (FRAC_MAX - 1));

    if (x < 0.0)
        r->frac = -r->frac;
}

double unpack(const struct dbl_packed *p)
{
    double xf, x;

    if (p->frac == 0)
        return 0.0;

    xf = ((double)(llabs(p->frac) - 1) / (FRAC_MAX - 1)) / 2.0;

    x = ldexp(xf + 0.5, p->exp);

    if (p->frac < 0)
        x = -x;

    return x;
}

答案 3 :(得分:5)

无论本机表示如何,您都可以在IEEE-754中进行便携式序列化:

int fwriteieee754(double x, FILE * fp, int bigendian)
{
    int                     shift;
    unsigned long           sign, exp, hibits, hilong, lowlong;
    double                  fnorm, significand;
    int                     expbits = 11;
    int                     significandbits = 52;

    /* zero (can't handle signed zero) */
    if(x == 0) {
        hilong = 0;
        lowlong = 0;
        goto writedata;
    }
    /* infinity */
    if(x > DBL_MAX) {
        hilong = 1024 + ((1 << (expbits - 1)) - 1);
        hilong <<= (31 - expbits);
        lowlong = 0;
        goto writedata;
    }
    /* -infinity */
    if(x < -DBL_MAX) {
        hilong = 1024 + ((1 << (expbits - 1)) - 1);
        hilong <<= (31 - expbits);
        hilong |= (1 << 31);
        lowlong = 0;
        goto writedata;
    }
    /* NaN - dodgy because many compilers optimise out this test
     * isnan() is C99, POSIX.1 only, use it if you will.
     */
    if(x != x) {
        hilong = 1024 + ((1 << (expbits - 1)) - 1);
        hilong <<= (31 - expbits);
        lowlong = 1234;
        goto writedata;
    }

    /* get the sign */
    if(x < 0) {
        sign = 1;
        fnorm = -x;
    } else {
        sign = 0;
        fnorm = x;
    }

    /* get the normalized form of f and track the exponent */
    shift = 0;
    while(fnorm >= 2.0) {
        fnorm /= 2.0;
        shift++;
    }
    while(fnorm < 1.0) {
        fnorm *= 2.0;
        shift--;
    }

    /* check for denormalized numbers */
    if(shift < -1022) {
        while(shift < -1022) {
            fnorm /= 2.0;
            shift++;
        }
        shift = -1023;
    } else {
        /* take the significant bit off mantissa */
        fnorm = fnorm - 1.0;
    }
    /* calculate the integer form of the significand */
    /* hold it in a  double for now */

    significand = fnorm * ((1LL << significandbits) + 0.5f);

    /* get the biased exponent */
    exp = shift + ((1 << (expbits - 1)) - 1);   /* shift + bias */

    /* put the data into two longs */
    hibits = (long)(significand / 4294967296);  /* 0x100000000 */
    hilong = (sign << 31) | (exp << (31 - expbits)) | hibits;
    lowlong = (unsigned long)(significand - hibits * 4294967296);

 writedata:
    /* write the bytes out to the stream */
    if(bigendian) {
        fputc((hilong >> 24) & 0xFF, fp);
        fputc((hilong >> 16) & 0xFF, fp);
        fputc((hilong >> 8) & 0xFF, fp);
        fputc(hilong & 0xFF, fp);

        fputc((lowlong >> 24) & 0xFF, fp);
        fputc((lowlong >> 16) & 0xFF, fp);
        fputc((lowlong >> 8) & 0xFF, fp);
        fputc(lowlong & 0xFF, fp);
    } else {
        fputc(lowlong & 0xFF, fp);
        fputc((lowlong >> 8) & 0xFF, fp);
        fputc((lowlong >> 16) & 0xFF, fp);
        fputc((lowlong >> 24) & 0xFF, fp);

        fputc(hilong & 0xFF, fp);
        fputc((hilong >> 8) & 0xFF, fp);
        fputc((hilong >> 16) & 0xFF, fp);
        fputc((hilong >> 24) & 0xFF, fp);
    }
    return ferror(fp);
}

在使用IEEE-754的机器(即常见案例)中,您需要做的就是fread()。否则,自己解码字节(sign * 2^(exponent-127) * 1.mantissa)

注意:在本机double比IEEE double更精确的系统中进行序列化时,您可能会遇到低位的逐个错误。

希望这有帮助。

答案 4 :(得分:3)

对于关于float的狭义问题,请注意您可能最终假设线的两端使用相同的浮点表示。考虑到IEEE-754的普遍使用,今天这可能是安全的,但请注意,一些当前的DSP(我相信黑色)使用不同的表示。在过去,浮点数的表示至少与硬件和库的制造商一样多,因此这是一个更大的问题。

即使使用相同的表示,它也可能不会以相同的字节顺序存储。这将需要在线上决定字节顺序,并在每一端调整代码。类型惩罚指针强制转换或联合将在实践中起作用。两者都在调用实现定义的行为,但只要你检查并测试这不是什么大问题。

也就是说,文本通常是您在平台之间传输浮点的朋友。诀窍是不要使用真正需要的太多字符来转换它。

总而言之,我建议认真考虑使用像XDR这样强大的库,已经存在了一段时间,并且已经针对所有尖锐的边角和边缘情况进行了擦拭

如果你坚持自己滚动,除了代表intfloat之外,还要注意double是否为16位,32位,甚至64位等微妙问题。

答案 5 :(得分:2)

更新后,您提到要使用UDP传输数据并询问最佳做法。我强烈建议将数据作为文本发送,甚至可能添加一些标记(XML)。在传输线上调试与字节序相关的错误是浪费每个人的时间

在问题的“最佳做法”部分,只需2美分

答案 6 :(得分:2)

您始终可以使用联合来序列化:

void serialize_double (unsigned char* buffer, double x) {
    int i;
    union {
        double         d;
        unsigned char  bytes[sizeof(double)];
    } u;

    u.d = x;
    for (i=0; i<sizeof(double); ++i)
        buffer[i] = u.bytes[i];
}

这并不比简单地将double的地址转换为char*更强大,但至少在整个代码中使用sizeof()可避免出现问题数据类型占用的字节数比您想象的多得多/少(如果您在double使用不同大小的平台之间移动数据,这无济于事。)

对于花车,只需将double的所有实例替换为float即可。您可以构建一个狡猾的宏来自动生成一系列这些函数,每个函数对应您感兴趣的数据类型。

答案 7 :(得分:1)

首先,你绝不能假设shortint等两边都有相同的宽度。使用两边都知道宽度的uint32_t等(无符号)类型会好得多。

然后为了确保你没有关于endianess的问题,有一些宏/函数ntoh htos等通常比你自己做的任何事情都要高效。 (在intel硬件上,它们只是一个汇编指令。)所以你不必编写转换函数,基本上它们已经存在,只需将buffer指针转换为正确整数类型的指针。

对于float,您可能会认为它们是32位并且在两侧具有相同的表示。所以我认为一个好的策略是使用指针转换为uint32_t*,然后使用与上面相同的策略。

如果您认为自己可能有float的不同表示形式,则必须分为尾数和指数。可能你可以使用frexpf