C / C ++中double / floattypes的二进制序列化的可移植性

C ++标准没有讨论float和doubletypes的底层布局,只讨论了它们应该表示的值的范围。 (这对签名types也是如此,这是两个赞美还是别的什么)

我的问题是:什么是技术用于序列化/反序列化PODtypes,如double和float以便携方式? 目前看起来唯一的方法就是让字面值代表(如“123.456”),double的ieee754布局在所有体系结构上都不是标准的。

Brian“Beej Jorgensen”Hall在他的“networking编程指南”(Guide to Network Programming)中给出了一些将floatdouble )包含到uint32_t (或uint64_t )的代码,以便能够在两台机器之间通过networking安全地传输数据表示。 它有一些限制,主要是不支持NaN和无穷大。

这是他的包装function:

 #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; } 

人类可读的格式有什么问题。

与二进制相比,它有两个优点:

  • 它是可读的
  • 它是便携式的
  • 它使支持真的很容易
    (因为你可以要求用户用他们最喜欢的编辑器来看它)
  • 这很容易修复
    (或在错误情况下手动调整文件)

坏处:

  • 这不是紧凑的
    如果这是一个真正的问题,你总是可以压缩它。
  • 提取/生成可能会稍微慢一些
    注意一个二进制格式可能也需要被标准化(参见htonl()

要以全精度输出双精度型:

 double v = 2.20; std::cout << std::setprecision(std::numeric_limits<double>::digits) << v; 

好。 我不相信这是确切的。 它可能会失去精度。

只需将二进制IEEE754表示forms写入磁盘,并将其logging为存储格式(以及字节序)。 然后,如果有必要的话,就可以将其转换为内部表示。

看一下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 */ 

glib链接

创build一个适当的串行器/解串器接口来写/读这个。

接口可以有几个实现,你可以testing你的选项。

如前所述,显而易见的select是:

  • IEEE754,如果体系结构直接支持,则写入/读取二进制块;如果体系结构不支持,则parsing该块
  • 文本:总是需要parsing。
  • 无论你还能想到什么

只要记住 – 一旦你有这个层,如果你只支持在内部使用这种格式的平台,你总是可以从IEEE754开始。 这样,只有当你需要支持不同的平台时,你才会有额外的努力! 不要做不必要的工作。

你应该把它们转换成一种你永远可以用来重新创build你的浮动/双打的格式。

这可以使用string表示forms,或者,如果您需要占用较less空间的内容,请使用ieee754(或您select的任何其他格式)表示您的数字,然后像使用string一样对其进行parsing

我认为答案“取决于”你的特定应用程序和它的性能概况是什么。

假设你有一个低延迟的市场数据环境,那么使用string是非常愚蠢的。 如果你所传递的信息是价格,那么双打(和它们的二进制表示)真的很难处理。 如果你真的不关心性能,你想要的是可见性(存储,传输),那么string是一个理想的select。

我实际上会select浮点数/双精度的整数尾数/指数表示 – 即在最早的机会下,将float / double转换为一对整数,然后传送。 然后你只需要担心整数的可移植性,以及各种例程(例如hton()例程)来处理转换。 还要将所有内容存储在最stream行的平台上(例如,如果您只使用linux,那么以big endian存储内容有什么意义?)

SQLite4使用新的格式来存储双打和浮动

  • 即使在缺乏IEEE 754二进制64位浮点数支持的平台上,它也可以可靠且一致地工作。
  • 货币计算通常可以精确地完成而不需要四舍五入。
  • 任何有符号或无符号的64位整数都可以精确表示。
  • 浮点范围和精度超过IEEE 754二进制64位浮点数。
  • 正负无穷和NaN(非数)具有明确的表示。

资料来源:

https://sqlite.org/src4/doc/trunk/www/design.wiki

https://sqlite.org/src4/doc/trunk/www/decimal.wiki

Interesting Posts