在C中读/写浮点类型时如何处理字节顺序差异?

我正在为我的应用程序设计一种文件格式,我显然希望它可以在big-endian和little-endian系统上运行。 我已经找到了使用htonlntohl来管理整数类型的工作解决方案,但是当我尝试使用floatdouble值进行相同操作时,我有点卡住了。

鉴于浮点表示如何工作的性质,我认为标准的字节顺序函数不适用于这些值。 同样,我甚至不完全确定传统意义上的字节顺序是否支配这些类型的字节顺序。

我需要的只是一致性。 写一个double出来的方法,并确保我在读回来时得到相同的值。我怎么能在C中做到这一点?

另一个选择可能是使用double frexp(double value, int *exp); (C99)将浮点值分解为归一化分数(在[ DBL_MANT_DIG )范围内)和2的积分幂。然后可以将分数乘以DBL_MANT_DIG得到整数,范围为[ DBL_MANT_DIG / 2, DBL_MANT_DIG ]。 然后你保存大小或小端的整数,无论​​你选择哪种格式。

加载保存的数字时,执行相反的操作并使用double ldexp(double x, int exp); 将重构的分数乘以2的幂。

FLT_RADIX = 2(几乎所有系统,我想?)和DBL_MANT_DIG <= 64时,这将最有效。

必须小心避免溢出。

doubles示例代码:

 #include  #include  #include  #include  #include  #if CHAR_BIT != 8 #error currently supported only CHAR_BIT = 8 #endif #if FLT_RADIX != 2 #error currently supported only FLT_RADIX = 2 #endif #ifndef M_PI #define M_PI 3.14159265358979324 #endif typedef unsigned char uint8; /* 10-byte little-endian serialized format for double: - normalized mantissa stored as 64-bit (8-byte) signed integer: negative range: (-2^53, -2^52] zero: 0 positive range: [+2^52, +2^53) - 16-bit (2-byte) signed exponent: range: [-0x7FFE, +0x7FFE] Represented value = mantissa * 2^(exponent - 53) Special cases: - +infinity: mantissa = 0x7FFFFFFFFFFFFFFF, exp = 0x7FFF - -infinity: mantissa = 0x8000000000000000, exp = 0x7FFF - NaN: mantissa = 0x0000000000000000, exp = 0x7FFF - +/-0: only one zero supported */ void Double2Bytes(uint8 buf[10], double x) { double m; long long im; // at least 64 bits int ie; int i; if (isnan(x)) { // NaN memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\xFF\x7F", 10); return; } else if (isinf(x)) { if (signbit(x)) // -inf memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10); else // +inf memcpy(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10); return; } // Split double into normalized mantissa (range: (-1, -0.5], 0, [+0.5, +1)) // and base-2 exponent m = frexp(x, &ie); // x = m * 2^ie exactly for FLT_RADIX=2 // frexp() can't fail // Extract most significant 53 bits of mantissa as integer m = ldexp(m, 53); // can't overflow because // DBL_MAX_10_EXP >= 37 equivalent to DBL_MAX_2_EXP >= 122 im = trunc(m); // exact unless DBL_MANT_DIG > 53 // If the exponent is too small or too big, reduce the number to 0 or // +/- infinity if (ie > 0x7FFE) { if (im < 0) // -inf memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10); else // +inf memcpy(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10); return; } else if (ie < -0x7FFE) { // 0 memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00", 10); return; } // Store im as signed 64-bit little-endian integer for (i = 0; i < 8; i++, im >>= 8) buf[i] = (uint8)im; // Store ie as signed 16-bit little-endian integer for (i = 8; i < 10; i++, ie >>= 8) buf[i] = (uint8)ie; } void Bytes2Double(double* x, const uint8 buf[10]) { unsigned long long uim; // at least 64 bits long long im; // ditto unsigned uie; int ie; double m; int i; int negative = 0; int maxe; if (!memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\xFF\x7F", 10)) { #ifdef NAN *x = NAN; #else *x = 0; // NaN is not supported, use 0 instead (we could return an error) #endif return; } if (!memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10)) { *x = -INFINITY; return; } else if (!memcmp(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10)) { *x = INFINITY; return; } // Load im as signed 64-bit little-endian integer uim = 0; for (i = 0; i < 8; i++) { uim >>= 8; uim |= (unsigned long long)buf[i] << (64 - 8); } if (uim <= 0x7FFFFFFFFFFFFFFFLL) im = uim; else im = (long long)(uim - 0x7FFFFFFFFFFFFFFFLL - 1) - 0x7FFFFFFFFFFFFFFFLL - 1; // Obtain the absolute value of the mantissa, make sure it's // normalized and fits into 53 bits, else the input is invalid if (im > 0) { if (im < (1LL << 52) || im >= (1LL << 53)) { #ifdef NAN *x = NAN; #else *x = 0; // NaN is not supported, use 0 instead (we could return an error) #endif return; } } else if (im < 0) { if (im > -(1LL << 52) || im <= -(1LL << 53)) { #ifdef NAN *x = NAN; #else *x = 0; // NaN is not supported, use 0 instead (we could return an error) #endif return; } negative = 1; im = -im; } // Load ie as signed 16-bit little-endian integer uie = 0; for (i = 8; i < 10; i++) { uie >>= 8; uie |= (unsigned)buf[i] << (16 - 8); } if (uie <= 0x7FFF) ie = uie; else ie = (int)(uie - 0x7FFF - 1) - 0x7FFF - 1; // If DBL_MANT_DIG < 53, truncate the mantissa im >>= (53 > DBL_MANT_DIG) ? (53 - DBL_MANT_DIG) : 0; m = im; m = ldexp(m, (53 > DBL_MANT_DIG) ? -DBL_MANT_DIG : -53); // can't overflow // because DBL_MAX_10_EXP >= 37 equivalent to DBL_MAX_2_EXP >= 122 // Find out the maximum base-2 exponent and // if ours is greater, return +/- infinity frexp(DBL_MAX, &maxe); if (ie > maxe) m = INFINITY; else m = ldexp(m, ie); // underflow may cause a floating-point exception *x = negative ? -m : m; } int test(double x, const char* name) { uint8 buf[10], buf2[10]; double x2; int error1, error2; Double2Bytes(buf, x); Bytes2Double(&x2, buf); Double2Bytes(buf2, x2); printf("%+.15E '%s' -> %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", x, name, buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9]); if ((error1 = memcmp(&x, &x2, sizeof(x))) != 0) puts("Bytes2Double(Double2Bytes(x)) != x"); if ((error2 = memcmp(buf, buf2, sizeof(buf))) != 0) puts("Double2Bytes(Bytes2Double(Double2Bytes(x))) != Double2Bytes(x)"); puts(""); return error1 || error2; } int testInf(void) { uint8 buf[10]; double x, x2; int error; x = DBL_MAX; Double2Bytes(buf, x); if (!++buf[8]) ++buf[9]; // increment the exponent beyond the maximum Bytes2Double(&x2, buf); printf("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X -> %+.15E\n", buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9], x2); if ((error = !isinf(x2)) != 0) puts("Bytes2Double(Double2Bytes(DBL_MAX) * 2) != INF"); puts(""); return error; } #define VALUE_AND_NAME(V) { V, #V } const struct { double value; const char* name; } testData[] = { #ifdef NAN VALUE_AND_NAME(NAN), #endif VALUE_AND_NAME(0.0), VALUE_AND_NAME(+DBL_MIN), VALUE_AND_NAME(-DBL_MIN), VALUE_AND_NAME(+1.0), VALUE_AND_NAME(-1.0), VALUE_AND_NAME(+M_PI), VALUE_AND_NAME(-M_PI), VALUE_AND_NAME(+DBL_MAX), VALUE_AND_NAME(-DBL_MAX), VALUE_AND_NAME(+INFINITY), VALUE_AND_NAME(-INFINITY), }; int main(void) { unsigned i; int errors = 0; for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++) errors += test(testData[i].value, testData[i].name); errors += testInf(); // Test subnormal values. A floating-point exception may be raised. errors += test(+DBL_MIN / 2, "+DBL_MIN / 2"); errors += test(-DBL_MIN / 2, "-DBL_MIN / 2"); printf("%d error(s)\n", errors); return 0; } 

输出( ideone ):

 +NAN 'NAN' -> 00 00 00 00 00 00 00 00 FF 7F +0.000000000000000E+00 '0.0' -> 00 00 00 00 00 00 00 00 00 00 +2.225073858507201E-308 '+DBL_MIN' -> 00 00 00 00 00 00 10 00 03 FC -2.225073858507201E-308 '-DBL_MIN' -> 00 00 00 00 00 00 F0 FF 03 FC +1.000000000000000E+00 '+1.0' -> 00 00 00 00 00 00 10 00 01 00 -1.000000000000000E+00 '-1.0' -> 00 00 00 00 00 00 F0 FF 01 00 +3.141592653589793E+00 '+M_PI' -> 18 2D 44 54 FB 21 19 00 02 00 -3.141592653589793E+00 '-M_PI' -> E8 D2 BB AB 04 DE E6 FF 02 00 +1.797693134862316E+308 '+DBL_MAX' -> FF FF FF FF FF FF 1F 00 00 04 -1.797693134862316E+308 '-DBL_MAX' -> 01 00 00 00 00 00 E0 FF 00 04 +INF '+INFINITY' -> FF FF FF FF FF FF FF 7F FF 7F -INF '-INFINITY' -> 00 00 00 00 00 00 00 80 FF 7F FF FF FF FF FF FF 1F 00 01 04 -> +INF +1.112536929253601E-308 '+DBL_MIN / 2' -> 00 00 00 00 00 00 10 00 02 FC -1.112536929253601E-308 '-DBL_MIN / 2' -> 00 00 00 00 00 00 F0 FF 02 FC 0 error(s) 

浮点值使用与整数值imho相同的字节顺序。 使用union将它们与相应的整数对应项重叠,并使用常见的hton函数:

 float htonf(float x) { union foo { float f; uint32_t i; } foo = { .f = x }; foo.i = htonl(foo.i); return foo.f; } 

根据应用程序,使用纯文本数据格式(可能是XML)可能是个好主意。 如果您不想浪费磁盘空间,可以压缩它。

XML可能是最便携的方式。

但是,看起来您已经构建了大部分解析器,但仍然存在浮动/双重问题。 我建议把它写成一个字符串(以你想要的精度)然后再读回来。

除非所有目标平台都使用IEEE-754浮点数(和双精度数),否则任何字节交换技巧都不适合您。

如果您保证您的实现始终以指定格式处理序列化浮点表示,那么您将没事(IEEE 754很常见)。

是的,架构可能会以不同的方式命令浮点数(例如,大端或小端)。 因此,您需要以某种方式指定字节顺序。 这可以是格式的规范或变量,并记录在文件的数据中。

最后一个主要缺陷是内置的对齐可能会有所不同。 您的硬件/处理器如何处理实际定义的数据。 因此,您可能需要交换数据/字节,然后将其移动到目标float / double

高性能标记表示,像HDF5甚至NetCDF这样的库可能有点重量级,除非你还需要这些库中的其他function。

只处理序列化的轻量级替代品将是例如XDR (另见维基百科描述 )。 如果没有足够的独立XDR库,许多操作系统都会提供开箱即用的XDR例程。