用C序列化double和float

如何在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; } 

编辑:

我从这个问题中找到了这些function

编辑2:

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

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

就你问题的“最佳实践”部分而言,只需2美分

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

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

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

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

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

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

这会将浮点值打包到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; } 

无论本机表示如何,您都可以在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更精确的系统中进行序列化时,您可能会遇到低位的逐个错误。

希望这可以帮助。

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

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

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

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

如果你坚持自己滚动,那么除了表示floatdouble之外,还要注意int是16位,32位,甚至64位等细微问题。

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

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

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

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

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

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

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

如果您认为float可能有不同的表示forms,则必须将其拆分为尾数和指数。 可能你可以使用frexpf