如何在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;
}
编辑:
找到了这些功能编辑2:
序列化的目的是将数据发送到UDP套接字,并保证即使字节序不同,也可以在其他机器上反序列化。是否有任何其他“最佳实践”来执行此功能,因为我必须序列化整数,双精度数,浮点数和字符*?
答案 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)
这会将浮点值打包到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;
}
答案 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这样强大的库,已经存在了一段时间,并且已经针对所有尖锐的边角和边缘情况进行了擦拭
如果你坚持自己滚动,除了代表int
和float
之外,还要注意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)
首先,你绝不能假设short
,int
等两边都有相同的宽度。使用两边都知道宽度的uint32_t
等(无符号)类型会好得多。
然后为了确保你没有关于endianess的问题,有一些宏/函数ntoh
htos
等通常比你自己做的任何事情都要高效。 (在intel硬件上,它们只是一个汇编指令。)所以你不必编写转换函数,基本上它们已经存在,只需将buffer
指针转换为正确整数类型的指针。
对于float
,您可能会认为它们是32位并且在两侧具有相同的表示。所以我认为一个好的策略是使用指针转换为uint32_t*
,然后使用与上面相同的策略。
如果您认为自己可能有float
的不同表示形式,则必须分为尾数和指数。可能你可以使用frexpf
。