C ++标准没有讨论float和double类型的底层布局,只讨论它们应该表示的值的范围。 (对于签名类型也是如此,这是两个赞美还是别的什么)
我的问题是:用于以可移植方式序列化/反序列化POD类型(如double和float)的技术是什么?目前,似乎唯一的方法就是按字面意思表示值(如“123.456”),double的ieee754布局在所有架构上都不是标准的。
答案 0 :(得分:27)
Brian“Beej Jorgensen”Hall在他的Guide to Network Programming中提供了一些代码,以便将float
(分别为double
)打包到uint32_t
(分别为uint64_t
)能够通过网络安全地在两台可能不同意其代表的机器之间传输它。它有一些限制,主要是它不支持NaN和无穷大。
这是他的包装功能:
#define pack754_32(f) (pack754((f), 32, 8))
#define pack754_64(f) (pack754((f), 64, 11))
uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
long double fnorm;
int shift;
long long sign, exp, significand;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (f == 0.0) return 0; // get this special case out of the way
// check sign and begin normalization
if (f < 0) { sign = 1; fnorm = -f; }
else { sign = 0; fnorm = f; }
// 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--; }
fnorm = fnorm - 1.0;
// calculate the binary form (non-float) of the significand data
significand = fnorm * ((1LL<<significandbits) + 0.5f);
// get the biased exponent
exp = shift + ((1<<(expbits-1)) - 1); // shift + bias
// return the final answer
return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}
答案 1 :(得分:5)
人类可读格式出了什么问题。
它比二进制有一些优势:
缺点:
htonl()
)以完全精度输出双精度:
double v = 2.20;
std::cout << std::setprecision(std::numeric_limits<double>::digits) << v;
行。我不相信这是完全准确的。它可能会失去精确度。
答案 2 :(得分:4)
只需将二进制IEEE754表示写入磁盘,并将其记录为存储格式(以及字节顺序)。然后,如果需要,可以将其转换为内部表示。
答案 3 :(得分:4)
看看glib 2中的(旧)gtypes.h文件实现 - 它包括以下内容:
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
union _GFloatIEEE754
{
gfloat v_float;
struct {
guint mantissa : 23;
guint biased_exponent : 8;
guint sign : 1;
} mpn;
};
union _GDoubleIEEE754
{
gdouble v_double;
struct {
guint mantissa_low : 32;
guint mantissa_high : 20;
guint biased_exponent : 11;
guint sign : 1;
} mpn;
};
#elif G_BYTE_ORDER == G_BIG_ENDIAN
union _GFloatIEEE754
{
gfloat v_float;
struct {
guint sign : 1;
guint biased_exponent : 8;
guint mantissa : 23;
} mpn;
};
union _GDoubleIEEE754
{
gdouble v_double;
struct {
guint sign : 1;
guint biased_exponent : 11;
guint mantissa_high : 20;
guint mantissa_low : 32;
} mpn;
};
#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
#error unknown ENDIAN type
#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
答案 4 :(得分:2)
创建一个适当的串行器/反序列化器接口,用于写入/读取它。
接口可以有多个实现,您可以测试您的选项。
如前所述,显而易见的选择是:
请记住 - 一旦有了这一层,如果你只支持内部使用这种格式的平台,你总是可以从IEEE754开始。这样,只有当您需要支持不同的平台时,您才会有额外的努力!不要做你不需要的工作。
答案 5 :(得分:1)
您应该将它们转换为您始终可以使用的格式,以便重新创建浮动/双打。
这可以使用字符串表示,或者,如果您需要占用较少空间的内容,则以ieee754(或您选择的任何其他格式)表示您的号码,然后解析,就像使用字符串。
答案 6 :(得分:0)
我认为答案“取决于”您的特定应用程序及其性能配置文件。
假设你有一个低延迟的市场数据环境,那么使用字符串就是坦率的愚蠢。如果您传达的信息是价格,那么双倍(以及它们的二进制表示)确实很难处理。在那里,如果你真的不关心性能,你想要的是可见性(存储,传输),那么字符串是理想的候选者。
我实际上会选择浮点数/双精度的积分尾数/指数表示 - 即在最早的时候,将float / double转换为一对整数,然后传输它。然后,您只需要担心整数的可移植性以及各种例程(例如hton()
例程来为您处理转换)。还将所有内容存储在最流行的平台的endianess中(例如,如果你只使用linux,那么以big endian存储东西的重点是什么?)
答案 7 :(得分:0)
SQLite4使用新格式存储双精度数和浮点数
- 即使在缺乏对IEEE 754 binary64浮点数的支持的平台上,它也能可靠且一致地工作。
- 通常可以精确地进行货币计算而不进行舍入。
- 可以准确表示任何有符号或无符号的64位整数。
- 浮点范围和精度超过IEEE 754 binary64浮点数。
- 正负无穷大和NaN(非数字)具有明确定义的表示。
来源:
答案 8 :(得分:0)
找到了这个旧线程。缺少一种解决多种情况的解决方案-使用固定点,在两端使用内置强制转换传递具有已知比例因子的整数。因此,您根本不必理会基础浮点表示。
当然有缺点。该解决方案假定您可以具有固定的缩放比例,并且仍然可以获得特定应用程序所需的范围和分辨率。此外,您在序列化端将浮点转换为固定点,然后在反序列化时转换回固定点,从而引入了两个舍入错误。 但是,多年来,我发现固定点几乎可以满足我的所有需求,而且速度也相当快。
固定点的典型情况是嵌入式系统或其他设备的通信协议。