代码之家  ›  专栏  ›  技术社区  ›  Trevor

用C序列化double和float

  •  12
  • Trevor  · 技术社区  · 14 年前

    如何在C中序列化double和float?

    我有以下代码用于序列化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套接字,并确保它可以在另一台计算机上进行反序列化,即使其endianness不同。考虑到我必须序列化int、double、float和char*,是否还有其他“最佳实践”来执行此功能?

    8 回复  |  直到 14 年前
        1
  •  2
  •   Jonathan Leffler vy32    6 年前

    更新之后,您提到将使用UDP传输数据,并询问最佳实践。我强烈建议将数据作为文本发送,甚至添加一些标记(XML)。在传输线上调试与endian相关的错误是浪费所有人的时间。

    只是我在你问题的“最佳实践”部分的2分

        2
  •  12
  •   S.C. Madsen    14 年前

    我记得在下面的例子中,我第一次看到的是“rsqrt”程序中的老地震源代码,其中包含了当时我看到的最酷的评论(google it,你会喜欢的)。

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

    我希望我已经正确理解了您的问题(和示例代码)。告诉我这是否有用?

        3
  •  11
  •   R.. GitHub STOP HELPING ICE    14 年前

    便携方式:使用 frexp 序列化(转换为整数尾数和指数)和 ldexp 反序列化

    简单的方法:假设在2010年,您关心的任何机器都使用了ieee float,声明一个与 float 元素与A uint32_t 元素,并使用整型序列化代码对浮点进行序列化。

    二进制文件讨厌的方式是:将所有内容序列化为文本,包括浮点。使用 "%a" printf格式说明符以获取十六进制浮点,它始终精确表示(前提是不使用类似的方法限制精度 "%.4a" )不受舍入误差的影响。你可以读回这些 strtod 或任何 scanf 功能系列。

        4
  •  8
  •   caf    14 年前

    这会将浮点值打包到 int long 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;
    }
    
        5
  •  5
  •   Michael F    14 年前

    您可以在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) .

    注意:在本机双精度比IEEE双精度高的系统中序列化时,您可能会在低位遇到一个错误。

    希望这有帮助。

        6
  •  3
  •   RBerteig Keith Adler    14 年前

    关于 float ,请注意,您可能会假定导线的两端对浮点使用相同的表示。考虑到IEEE-754的广泛使用,这在今天可能是安全的,但是请注意,一些当前的DSP(我相信Blackfins)使用了不同的表示。在过去,对于浮点的表示至少和硬件和库的制造商一样多,所以这是一个更大的问题。

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

    也就是说,文本常常是平台间转移浮点值的朋友。诀窍是不要使用太多真正需要转换回的字符。

    总之,我建议您认真考虑一下使用图书馆,例如 XDR 这是强大的,已经存在一段时间,并已擦了所有尖锐的角落和边缘案件。

    如果你坚持自己动手,要注意一些微妙的问题,比如 int 是16位、32位或甚至64位,除了表示 浮动 double .

        7
  •  2
  •   bta    14 年前

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

    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 到A char* ,但至少通过使用 sizeof() 在整个代码中,当数据类型占用的字节比您想象的多/少时,您可以避免出现问题(如果您在使用不同大小的平台之间移动数据 双重的 )

    对于float,只需替换 双重的 具有 float . 您可以构建一个巧妙的宏来自动生成一系列这些函数,每个函数对应您感兴趣的数据类型。

        8
  •  1
  •   Jens Gustedt    14 年前

    首先,你不应该假定 short , int 两侧宽度相同。最好使用 uint32_t 两侧宽度已知的ETC(无符号)类型。

    那么为了确保您不存在endianes的问题,这里有宏/函数 ntoh htos 通常比你自己能做的任何事都更有效率。(在Intel硬件上,它们只是一条汇编指令。)因此,您不必编写转换函数,基本上它们已经存在,只需强制转换 buffer 指向正确整数类型指针的指针。

    为了 float 您可能会假设它们是32位的,并且在两侧具有相同的表示。所以我认为一个好的策略是使用 uint32_t* 然后是同样的策略。

    如果你认为你可能对 浮动 你必须分成尾数和指数。也许你可以用 frexpf 为此。