【问题标题】:C - Serialization of the floating point numbers (floats, doubles)C - 浮点数的序列化(浮点数、双精度数)
【发布时间】:2010-12-19 15:48:27
【问题描述】:

如何将浮点数转换为字节序列,以便可以持久化到文件中?这种算法必须快速且高度可移植。它还必须允许相反的操作,反序列化。如果每个值(持久空间)只需要非常微小的多余位,那就太好了。

【问题讨论】:

  • 您希望移植到哪些系统?
  • 它必须独立于底层架构,例如它可以是 ARM-7、PowerPC、Microblaze、OpenRISC 或只是 x86。
  • 这是作业吗?从您的 cmets 看来,确实如此。
  • 有些问题很有趣,即使它们恰好是从家庭作业中出现的。如果他们曾经是任何人的家庭作业的主题,那么简单地禁止本网站上的所有事实和主题将意味着删除一半,我想......

标签: c floating-point floating-point-conversion


【解决方案1】:

sprintf, fprintf ?没有比这更便携的了。

【讨论】:

  • 这不是有效的解决方案,它需要比 RAM 中表示的相同数字更多的持久空间
  • 好吧,那你为什么不这样做呢?
  • 它可能需要更多空间,但它既是人类可读的,也是机器可读的,与字节序无关,并且在所需精度方面理论上是无限的。
  • 更重要的是@dreamlax,它与浮点格式无关。
  • 一开始看起来不错,但暗示可能很严重(或不严重,取决于使用情况):您不能总是以十进制格式存储浮点数。这意味着以ascii十进制格式存储,无论添加多少十进制数字,都不能保证读回的数字与存储的数字相同。
【解决方案2】:

您需要什么级别的可移植性?如果要在与生成文件的操作系统相同的计算机上读取文件,那么使用二进制文件并仅保存和恢复位模式应该可以工作。否则,正如boytheo 所说,ASCII 是你的朋友。

【讨论】:

    【解决方案3】:

    转换为 ascii 表示将是最简单的,但如果您需要处理大量的浮点数,那么您当然应该使用二进制。但是,如果您关心可移植性,这可能是一个棘手的问题。浮点数在不同机器中的表示方式不同。

    如果您不想使用罐装库,那么您的浮点二进制序列化器/反序列化器将只需要在每个位的位置及其代表的内容上有“合同”。

    这里有一个有趣的网站可以帮助解决这个问题:link

    【讨论】:

      【解决方案4】:

      假设您使用的是主流编译器,C 和 C++ 中的浮点值遵循 IEEE 标准,并且当以二进制形式写入文件时,可以在任何其他平台上恢复,前提是您使用相同的字节字节序进行写入和读取.所以我的建议是:选择一个字节序,在写之前或者读之后,检查这个字节序是否和当前平台的一样;如果没有,只需交换字节。

      【讨论】:

      • 根据 C99 规范附件 F,符合要求的实现应定义 __STDC_IEC_559__,原则上可用作编译时检查,但在实践中无用,因为 gcc ( gcc.gnu.org/c99status.html,向下滚动到“更多问题”)
      • 编译器不一定规定 IEEE 浮点格式。不幸的是,仍有一些计算机使用其他格式(VAX/Alpha,IBM)。但是 +1 确保您拥有正确的字节顺序。
      • 对,但他们必须知道平台使用的格式才能在 RTL 中支持它。此外,许多平台(现在尤其是嵌入式平台)没有数学协处理器,因此它们确实在随附的仿真库中规定了格式。所以我认为参考编译器会更容易。
      • 难道不将那些不支持IEEE标准的平台视为例外,当需要它们的(稀有)版本时,只在那里进行必要的转换?这是一篇关于差异的好文章:codeproject.com/KB/applications/libnumber.aspx
      【解决方案5】:

      “便携”是什么意思?

      为了可移植性,请记住将数字保持在标准中定义的限制范围内:使用超出这些限制的单个数字,所有可移植性都会付诸东流。

      double planck_time = 5.39124E-44; /* second */
      

      5.2.4.2.2 浮动类型的特点

      [...] 10 下表中给出的值应替换为常数 具有实现定义值的表达式 [...] 11 下表中给出的值应替换为常数 具有实现定义值的表达式 [...] 12 下表中给出的值应替换为常数 具有实现定义(正)值的表达式 [...] [...]

      注意所有这些子句中的implementation-defined

      【讨论】:

        【解决方案6】:

        您始终可以以固定字节顺序(小端或大端)转换为 IEEE-754 格式。对于大多数机器来说,这将不需要任何东西或简单的字节交换来序列化和反序列化。本身不支持 IEEE-754 的机器需要编写转换器,但使用 ldexpfrexp(标准 C 库函数)和位改组并不太难。

        【讨论】:

        • FP 标准缺少一些 IEEE 的“特性”,因此出现了问题。即 VAX 和 IBM 浮点格式……您正处于一个充满伤害的世界。角落案例。值得庆幸的是,人们编写了出色的转换器,可以优雅地处理这些情况(我在看着你 USGS!我欠你一杯啤酒)。
        • 符合 ANSI 的 frexp 函数为您隐藏了大部分内容。当然,您最终可能会遇到序列化和反序列化为您提供(接近但)不同值的情况。
        【解决方案7】:

        这可能会给您一个良好的开端 - 它将浮点值打包到 intlong long 对中,然后您可以按通常的方式对其进行序列化。

        #define FRAC_MAX 9223372036854775807LL /* 2**63 - 1 */
        
        struct dbl_packed
        {
            int exp;
            long long frac;
        };
        
        void pack(double x, struct dbl_packed *r)
        {
            double xf = fabs(frexp(x, &r->exp)) - 0.5;
        
            if (xf < 0.0)
            {
                r->frac = 0;
                return;
            }
        
            r->frac = 1 + (long long)(xf * 2.0 * (FRAC_MAX - 1));
        
            if (x < 0.0)
                r->frac = -r->frac;
        }
        
        double unpack(const struct dbl_packed *p)
        {
            double xf, x;
        
            if (p->frac == 0)
                return 0.0;
        
            xf = ((double)(llabs(p->frac) - 1) / (FRAC_MAX - 1)) / 2.0;
        
            x = ldexp(xf + 0.5, p->exp);
        
            if (p->frac < 0)
                x = -x;
        
            return x;
        }
        

        【讨论】:

          【解决方案8】:

          此版本每一个浮点值仅超过一个字节来指示字节顺序。但我认为,它仍然不是很便携。

          #include <stdio.h>
          #include <string.h>
          #include <stdlib.h>
          #include <ctype.h>
          
          #define LITEND      'L'
          #define BIGEND      'B'
          
          typedef short               INT16;
          typedef int                 INT32;
          typedef double              vec1_t;
          
           typedef struct {
              FILE            *fp;
          } WFILE, RFILE;
          
          #define w_byte(c, p)    putc((c), (p)->fp)
          #define r_byte(p)       getc((p)->fp)
          
          static void w_vec1(vec1_t v1_Val, WFILE *p)
          {
              INT32   i;
              char    *pc_Val;
          
              pc_Val = (char *)&v1_Val;
          
              w_byte(LITEND, p);
              for (i = 0; i<sizeof(vec1_t); i++)
              {
                  w_byte(pc_Val[i], p);
              }
          }
          
          
          static vec1_t r_vec1(RFILE *p)
          {
              INT32   i;
              vec1_t  v1_Val;
              char    c_Type,
                      *pc_Val;
          
              pc_Val = (char *)&v1_Val;
          
              c_Type = r_byte(p);
              if (c_Type==LITEND)
              {
                  for (i = 0; i<sizeof(vec1_t); i++)
                  {
                      pc_Val[i] = r_byte(p);
                  }
              }
              return v1_Val;
          }
          
          int main(void)
          {
              WFILE   x_FileW,
                      *px_FileW = &x_FileW;
              RFILE   x_FileR,
                      *px_FileR = &x_FileR;
          
              vec1_t  v1_Val;
              INT32   l_Val;
              char    *pc_Val = (char *)&v1_Val;
              INT32   i;
          
              px_FileW->fp = fopen("test.bin", "w");
              v1_Val = 1234567890.0987654321;
              printf("v1_Val before write = %.20f \n", v1_Val);
              w_vec1(v1_Val, px_FileW);
              fclose(px_FileW->fp);
          
              px_FileR->fp = fopen("test.bin", "r");
              v1_Val = r_vec1(px_FileR);
              printf("v1_Val after read = %.20f \n", v1_Val);
              fclose(px_FileR->fp);
              return 0;
          }
          

          【讨论】:

          • 它只能移植到共享相同浮点格式的机器上。走在这条路上,我会给你以下建议:在 Little Endian IEEE-754 上标准化,并让其他人在必要时转换到/从它转换。最后你会更快乐。您将通过严格的标准获得可移植性。
          【解决方案9】:

          fwrite(), fread()?您可能需要二进制文件,并且您不能将字节打包得更紧,除非您想牺牲您将在程序中执行的精度,然后再牺牲 fwrite() fread() ;浮动一个;双乙; a=(浮动)b; fwrite(&a,1,sizeof(a),fp);

          如果您携带不同的浮点格式,它们可能无法直接转换为二进制,因此您可能必须将这些位分开并执行数学运算,这就是幂等。IEEE 754 是一个使用可怕的标准,但广泛使用,因此可以最大限度地减少工作量。

          【讨论】:

          • 这个问题明确询问了一种可移植的方法,这显然不是。
          • “浮点”根据定义是不可移植的,格式很多,具体格式没有指定。 C 也不是很便携,这个问题充其量是有缺陷的。
          【解决方案10】:

          我们开始吧。

          便携式 IEEE 754 序列化/反序列化应该 无论机器的内部浮点数如何,都可以工作 表示。

          https://github.com/MalcolmMcLean/ieee754

          /*
          * read a double from a stream in ieee754 format regardless of host
          *  encoding.
          *  fp - the stream
          *  bigendian - set to if big bytes first, clear for little bytes
          *              first
          *
          */
          double freadieee754(FILE *fp, int bigendian)
          {
              unsigned char buff[8];
              int i;
              double fnorm = 0.0;
              unsigned char temp;
              int sign;
              int exponent;
              double bitval;
              int maski, mask;
              int expbits = 11;
              int significandbits = 52;
              int shift;
              double answer;
          
              /* read the data */
              for (i = 0; i < 8; i++)
                  buff[i] = fgetc(fp);
              /* just reverse if not big-endian*/
              if (!bigendian)
              {
                  for (i = 0; i < 4; i++)
                  {
                      temp = buff[i];
                      buff[i] = buff[8 - i - 1];
                      buff[8 - i - 1] = temp;
                  }
              }
              sign = buff[0] & 0x80 ? -1 : 1;
              /* exponet in raw format*/
              exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);
          
              /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
              bitval = 0.5;
              maski = 1;
              mask = 0x08;
              for (i = 0; i < significandbits; i++)
              {
                  if (buff[maski] & mask)
                      fnorm += bitval;
          
                  bitval /= 2.0;
                  mask >>= 1;
                  if (mask == 0)
                  {
                      mask = 0x80;
                      maski++;
                  }
              }
              /* handle zero specially */
              if (exponent == 0 && fnorm == 0)
                  return 0.0;
          
              shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
              /* nans have exp 1024 and non-zero mantissa */
              if (shift == 1024 && fnorm != 0)
                  return sqrt(-1.0);
              /*infinity*/
              if (shift == 1024 && fnorm == 0)
              {
          
          #ifdef INFINITY
                  return sign == 1 ? INFINITY : -INFINITY;
          #endif
                  return  (sign * 1.0) / 0.0;
              }
              if (shift > -1023)
              {
                  answer = ldexp(fnorm + 1.0, shift);
                  return answer * sign;
              }
              else
              {
                  /* denormalised numbers */
                  if (fnorm == 0.0)
                      return 0.0;
                  shift = -1022;
                  while (fnorm < 1.0)
                  {
                      fnorm *= 2;
                      shift--;
                  }
                  answer = ldexp(fnorm, shift);
                  return answer * sign;
              }
          }
          
          
          /*
          * 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);
          }
          

          【讨论】:

          • 已解决。现在代码。(链接也有单精度,但它很简单)。
          猜你喜欢
          • 1970-01-01
          • 2023-02-20
          • 1970-01-01
          • 1970-01-01
          • 2011-10-24
          • 2018-02-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多