C – 浮点数的序列化(浮点数,双精度数)
如何将浮点数转换为字节序列,以便它可以保存在文件中? 这种算法必须快速且高度便携。 它必须允许相反的操作,反序列化。 如果每个值(持久空间)只需要非常微小的多余位,那就太好了。
假设您正在使用主流编译器,C和C ++中的浮点值遵循IEEE标准,并且当以二进制forms写入文件时,可以在任何其他平台中恢复,前提是您使用相同的字节endianess进行写入和读取。 所以我的建议是:选择一个选择的结尾,在写作之前或阅读之后,检查该结尾是否与当前平台相同; 如果没有,只需交换字节。
您始终可以按固定的字节顺序(小端或大端)转换为IEEE-754格式。 对于大多数机器来说,根本不需要任何东西,或者需要简单的字节交换来序列化和反序列化。 一台本机不支持IEEE-754的机器需要编写一个转换器,但使用ldexp和frexp(标准C库函数)和位混洗这样做并不太难。
这可能会给你一个良好的开端 – 它将一个浮点值打包到一个int
和long long
对中,然后你可以按照通常的方式进行序列化。
#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; }
转换为ascii表示将是最简单的,但如果你需要处理大量的浮点数,那么当然你应该使用二进制。 但如果您关心可移植性,这可能是一个棘手的问题。 浮点数在不同的机器中表示不同。
如果您不想使用固定库,那么您的浮点二进制序列化器/解串器将只需要在每个位的位置和它代表的位置上签订“合同”。
这是一个有趣的网站,以帮助: 链接 。
你是什么意思,“便携式”?
为了便于携带,请记住将数字保持在标准中定义的限制范围内:使用超出这些限制的单个数字,并将所有可移植性排除在外。
double planck_time = 5.39124E-44; /* second */
5.2.4.2.2浮点类型的特征
[...] 10以下清单中给出的数值应由常数代替 具有实现定义值的表达式[...] 11以下清单中给出的数值应由常数代替 具有实现定义值的表达式[...] 12以下列表中给出的值应替换为常数 具有实现定义(正)值的表达式[...] [...]
请注意所有这些子句中定义的实现 。
sprintf,fprintf? 你没有那么便携。
您需要什么级别的便携性? 如果要在与生成的操作系统相同的计算机上读取文件,则使用二进制文件并保存和恢复位模式应该可以正常工作。 另外,正如boytheo所说,ASCII是你的朋友。
此版本每个浮点值仅超过一个字节以指示字节序。 但我认为,它仍然不是很便携。
#include #include #include #include #define LITEND 'L' #define BIGEND 'B' typedef short INT16; typedef int INT32; typedef double vec1_t; typedef struct { FILE *fp; } WFILE, RFILE; #define w_byte(c, p) putc((c), (p)->fp) #define r_byte(p) getc((p)->fp) static void w_vec1(vec1_t v1_Val, WFILE *p) { INT32 i; char *pc_Val; pc_Val = (char *)&v1_Val; w_byte(LITEND, p); for (i = 0; ifp = fopen("test.bin", "w"); v1_Val = 1234567890.0987654321; printf("v1_Val before write = %.20f \n", v1_Val); w_vec1(v1_Val, px_FileW); fclose(px_FileW->fp); px_FileR->fp = fopen("test.bin", "r"); v1_Val = r_vec1(px_FileR); printf("v1_Val after read = %.20f \n", v1_Val); fclose(px_FileR->fp); return 0; }
fwrite(),fread()? 您可能需要二进制文件,并且除非您想牺牲在程序中执行的精度然后fwrite()fread(),否则您无法将字节打包得更紧。 漂浮一个; 双b; α=(浮点)B; 的fwrite(&A,1,的sizeof(a)中,FP);
如果您携带不同的浮点格式,它们可能无法以直接二进制方式进行转换,因此您可能必须分开这些位并执行数学运算,这可能会加上这个等等.IEEE 754是一个可怕的标准使用但广泛,所以它会尽量减少努力。
开始了。
便携式IEEE 754序列化/反序列化,无论机器的内部浮点表示如何,都应该工作。
https://github.com/MalcolmMcLean/ieee754
/* * read a double from a stream in ieee754 format regardless of host * encoding. * fp - the stream * bigendian - set to if big bytes first, clear for little bytes * first * */ double freadieee754(FILE *fp, int bigendian) { unsigned char buff[8]; int i; double fnorm = 0.0; unsigned char temp; int sign; int exponent; double bitval; int maski, mask; int expbits = 11; int significandbits = 52; int shift; double answer; /* read the data */ for (i = 0; i < 8; i++) buff[i] = fgetc(fp); /* just reverse if not big-endian*/ if (!bigendian) { for (i = 0; i < 4; i++) { temp = buff[i]; buff[i] = buff[8 - i - 1]; buff[8 - i - 1] = temp; } } sign = buff[0] & 0x80 ? -1 : 1; /* exponet in raw format*/ exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4); /* read inthe mantissa. Top bit is 0.5, the successive bits half*/ bitval = 0.5; maski = 1; mask = 0x08; for (i = 0; i < significandbits; i++) { if (buff[maski] & mask) fnorm += bitval; bitval /= 2.0; mask >>= 1; if (mask == 0) { mask = 0x80; maski++; } } /* handle zero specially */ if (exponent == 0 && fnorm == 0) return 0.0; shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */ /* nans have exp 1024 and non-zero mantissa */ if (shift == 1024 && fnorm != 0) return sqrt(-1.0); /*infinity*/ if (shift == 1024 && fnorm == 0) { #ifdef INFINITY return sign == 1 ? INFINITY : -INFINITY; #endif return (sign * 1.0) / 0.0; } if (shift > -1023) { answer = ldexp(fnorm + 1.0, shift); return answer * sign; } else { /* denormalised numbers */ if (fnorm == 0.0) return 0.0; shift = -1022; while (fnorm < 1.0) { fnorm *= 2; shift--; } answer = ldexp(fnorm, shift); return answer * sign; } } /* * write a double to a stream in ieee754 format regardless of host * encoding. * x - number to write * fp - the stream * bigendian - set to write big bytes first, elee write litle bytes * first * Returns: 0 or EOF on error * Notes: different NaN types and negative zero not preserved. * if the number is too big to represent it will become infinity * if it is too small to represent it will become zero. */ 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, but *there is no portable isnan() */ 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; } /* out of range. Set to infinity */ else if (shift > 1023) { hilong = 1024 + ((1 << (expbits - 1)) - 1); hilong <<= (31 - expbits); hilong |= (sign << 31); lowlong = 0; goto writedata; } else fnorm = fnorm - 1.0; /* take the significant bit off mantissa */ /* 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 (for convenience) */ hibits = (long)(significand / 4294967296); hilong = (sign << 31) | (exp << (31 - expbits)) | hibits; x = significand - hibits * 4294967296; 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); }