我有一个库,可以以文本形式保存到磁盘上的浮点数据。由于可移植性问题,他们似乎已经完成了这项工作,但是由于磁盘的大量使用,我编写了一个函数来将浮点的二进制表示直接保存到磁盘。我知道这并不能保证100%的可移植性,但我只能在x86(_64)Linux / Windows PC上运行(也可能在Mac和BSD中)。
有没有办法至少检查程序理解的浮点格式是否也适用于系统?我应该期望以二进制形式处理浮点数据有多少不兼容性?
答案 0 :(得分:2)
有没有办法至少检查程序理解的浮点格式是否也适用于系统?
测试1:sizeof。测试2:在磁盘文件的标题中保存一个魔术浮点值,并在从磁盘读取二进制数据后检查程序是否具有正确的值。这应该足够安全。
我应该期望以二进制形式处理浮点数据有多少不兼容性?
很少。如果你正在说,你只使用一种硬件架构(x86),你会没事的。如果您拥有一组有限的受支持架构 - 只需测试所有这些架构。在x86上,每个人都将使用硬件浮点,这限制了它们的创造性(几乎根本没有)。即使在架构之间,我所知道的使用IEEE 754浮点的人对于相同的字节序也具有相同的二进制表示。
浮点有一个奇怪的问题,就是它们的二进制磁盘/线上表示没有广泛使用的标准。话虽这么说,我看过的每个人都会做两件事之一:串或者将位模式存储在一个大小相等的整数中,调整字节顺序,粗暴地转换为浮动。
答案 1 :(得分:2)
查找二进制可移植性网站。 https://github.com/MalcolmMcLean/ieee754
编写IEEE 754可移植的功能很长,但它只是 剪切和粘贴工作。 还有一个浮动版本。
/*
* 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);
}
答案 2 :(得分:1)
您可以在标题<float.h>
,page 46: 5.2.4.2.2 Characteristics of floating types中查看新的(C11)和旧宏。
答案 3 :(得分:-1)
一般来说,您可以直接读取和写入二进制数据:IEEE754二进制交换格式在几个小众领域之外几乎是标准的。您可以使用__STDC_IEC_559__
宏进行检查。
As noted in this question,规范没有指定的一件事是比特到字节的精确映射,因此有可能endianness issues(尽管如果你只使用x86 / x86_64可能不会)。在流的开头包含一个检查浮点值可能是一个好主意(请注意,检查整数的字节顺序是不够的,因为从技术上讲,它可能对整数和浮点具有不同的字节顺序)。
如果您正在编写文本,则需要考虑的另一种选择是hex float format,其读取/写入速度比十进制格式快得多(尽管不如原始二进制交换格式快)。不幸的是,虽然它是IEEE和C-99规范的一部分,但MSVC编译器很难支持它(尽管现在它可能会改变它是C ++的一部分)。