在C中读取/写入浮点类型时,如何处理字节顺序差异?
我正在为我的应用程序设计一种文件格式,我显然希望它能在big-endian和little-endian系统上工作。我已经找到了使用在C中读取/写入浮点类型时,如何处理字节顺序差异?,c,floating-point,endianness,C,Floating Point,Endianness,我正在为我的应用程序设计一种文件格式,我显然希望它能在big-endian和little-endian系统上工作。我已经找到了使用htonl和ntohl管理整数类型的有效解决方案,但在尝试使用float和double值执行相同操作时,我有点卡住了 考虑到浮点表示法的工作性质,我假设标准字节顺序函数不能处理这些值。类似地,我甚至不能完全确定传统意义上的endianness是否是控制这些类型字节顺序的因素 我所需要的就是一致性。一种将double写出来,并确保在读回时得到相同值的方法。如何在C语言中
htonl
和ntohl
管理整数类型的有效解决方案,但在尝试使用float
和double
值执行相同操作时,我有点卡住了
考虑到浮点表示法的工作性质,我假设标准字节顺序函数不能处理这些值。类似地,我甚至不能完全确定传统意义上的endianness是否是控制这些类型字节顺序的因素
我所需要的就是一致性。一种将
double
写出来,并确保在读回时得到相同值的方法。如何在C语言中实现这一点?根据应用程序的不同,最好使用纯文本数据格式(可能是XML)。如果不想浪费磁盘空间,可以对其进行压缩。浮点值使用与整数值imho相同的字节顺序。使用活接头将其与相应的整体对应件重叠,并使用常见的hton功能:
float htonf(float x) {
union foo {
float f;
uint32_t i;
} foo = { .f = x };
foo.i = htonl(foo.i);
return foo.f;
}
XML可能是实现这一点的最可移植的方式 然而,看起来您已经构建了大部分解析器,但是仍然停留在浮点/双精度问题上。我建议把它写成一个字符串(精确到你想要的程度),然后把它读回来
除非您的所有目标平台都使用IEEE-754浮点数(和双精度浮点数),否则字节交换技巧将不适用于您。如果您保证您的实现始终以指定格式处理序列化的浮点表示,那么您就可以了(IEEE 754很常见) 是的,体系结构对浮点数的排序可能不同(例如,以大端或小端为单位)。因此,您需要以某种方式指定endianness。这可以是格式的规范或变量,并记录在文件的数据中
最后一个主要陷阱是,内置的对齐方式可能会有所不同。硬件/处理器处理对齐错误数据的方式由实现定义。因此,您可能需要交换数据/字节,然后将其移动到目标
float
/double
另一个选项是使用double-frexp(双值,int*exp)
从
(C99)将浮点值分解为标准化分数(在[0.5,1]范围内)和2的整数幂。然后可以将分数乘以FLT\u基数
DBL\u MANT\u DIG
,得到范围内的整数[FLT_RADIX
DBL_MANT_DIG
/2,FLT_RADIX
DBL_MANT_DIG
)。然后保存两个整数,无论是大尾数还是小尾数,以您的格式选择
加载保存的数字时,执行相反的操作,并使用double ldexp(double x,int exp);
将重建的分数乘以2的幂
当
FLT_RADIX
=2(我想几乎所有的系统)和DBL_MANT_DIG
像HDF5甚至NetCDF这样的库可能有点重时,这将是最好的,正如高性能标记所说,除非您还需要这些库中的其他可用功能
一个只处理序列化的较轻的替代方案是(另请参阅)。许多操作系统都提供现成的XDR例程,如果这还不够的话。还存在独立的XDR库。将其保存为文本不是一个选项?@qPCR4vir可能会降低很多性能。库如HDF5()为您解决所有这些问题。我怀疑HDF5对于您的需求来说可能有点重。与任何序列化问题一样:指定一个wire格式,然后为每个平台编写编写器和读取器。我有一种感觉,对于至少一个平台,这将是微不足道的,而对于其他平台,它最多需要一个字节交换。等等……还有平台现在的表单不使用IEEE 754浮点?我不认为对RAM中IEEE-754浮点/双精度中的位的顺序有任何保证。它可以是任何东西,您不应该直接操作它的内容。这是一篇有趣的帖子,内容是关于如何确保平台的双精度实现符合IEEE-754:@FUZxxl-早于IEEE浮点运算的一个重要平台是IBM大型机。从20世纪60年代开始,它们仍然有自己的格式。但OP可能不关心这些格式。
%a
可能比%f
/%e
/%g
在将浮点值写入文本时更好。可读性较差,但应避免trun小数位数的组合或小数位数太多。在某些平台上,整数和浮点具有不同的endianness。但我们可能不想再支持这些。太好了,谢谢。我想我唯一关心的是溢出。您建议如何处理它们?此外,此方法准确吗?如果浮点值是base-2,则此方法不正确o准确地说。当CPU的格式的指数范围比格式的指数范围大或相反时,您可能会遇到唯一的溢出。您可能需要检查这一点,并使用特殊的无穷大值或最大值(如果不支持无穷大)。您可能在位数方面有类似的问题尾数中的s/位。如果它不适合CPU或文件插槽,则需要对其进行截断或舍入。您可以优化IEEE-754的代码,以便如果CPU支持IEEE-754,则无需进行任何特殊检查。嗯,好的。您介意发布一些(伪)代码吗代码?我试过实现它,但不是在所有情况下都能正常工作。事实上,不管怎样,我成功了,错误就在我的代码中。
#include <limits.h>
#include <float.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#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;
}
+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)