【问题标题】:best way to output a full precision double into a text file将全精度双精度输出到文本文件的最佳方法
【发布时间】:2011-06-06 07:56:17
【问题描述】:

我需要使用现有的文本文件来存储一些非常精确的值。回读时,这些数字基本上需要与最初写入的数字完全相同。现在,普通人会使用二进制文件……出于多种原因,在这种情况下这是不可能的。

所以...你们中的任何人有没有一种将双精度编码为字符串的好方法(除了提高精度)。我的第一个想法是将双精度转换为 char[] 并写出字符。我不认为这会起作用,因为有些字符是不可见的,会发出声音,甚至终止字符串('\0'...我在跟你说话!)

想法?

[编辑] - 一旦我确定哪个解决方案最适合我,我会将其中一个标记为“最佳”解决方案。

【问题讨论】:

  • 如果你想便携,你可以假设浮点数的表示形式(在表示的标准中没有定义)。因此,ONLY 的便携方式就是尽可能精确地打印数字。现在,如果您想放弃可移植性,那么您可以使用二进制格式(如果您愿意,可以使用 Base64 编码)。但是,在转换为特定于平台的浮点格式时,您将失去精度(除非它与源系统完全相同)。但是,您对全精度打印一无所获。
  • 要考虑的一点是,您的编译器可能会为 CPU 寄存器分配精度超过 64 位的双精度。当这些值被写入内存以准备将它们写入磁盘时,它们将被截断为 64 位。因此,即使您将双精度值保存为二进制并读入,也不能保证读取的值 == 原始值。
  • 无论您使用何种编码格式,在加载到可用精度较低的系统时,您都会失去精度。你关心它是否是人类可读的吗?您是否关心保存/加载是否快速?你在乎需要存储多少字节吗?请注意,在许多高精度系统上,您可以使用 long double 获得比 double 更高的精度
  • @fuzzyTew:现在很少有专业格式是二进制的,这是有原因的。当我们没有空间时,我们尝试过,但从长远来看它不会得到回报。人类可读的形式更容易使用和维护,而且人类可读的形式不会丢失任何精度。压缩是当今世界选择格式的一个非常糟糕的理由(没有一些非常具体的理由)。
  • 我不会使用long double。整个 x87 指令集在较新的处理器上被视为已弃用。例如,64bWin7 似乎不允许在内核中使用 x87,而英特尔、AMD 和微软强烈反对使用它。他们都建议改用 SSE2 数学。所以 10byte double 似乎已经过时了。

标签: c++ string file-io character-encoding file-format


【解决方案1】:

要在 C++ 中无损地打印一长串数字(在同一个架构中写入和读取),我使用这个(对于 doubles):

#include<iostream>
#include<iomanip>
#include<limits>
#include<cmath>

#include<sstream>
int main(){
std::ostringstream oss;

int prec = std::numeric_limits<double>::digits10+2; // generally 17

int exponent_digits = std::log10(std::numeric_limits<double>::max_exponent10)+1; // generally 3
int exponent_sign   = 1; // 1.e-123
int exponent_symbol = 1; // 'e' 'E'
int digits_sign = 1;
int digits_dot = 1; // 1.2

int division_extra_space = 1;
int width = prec + exponent_digits + digits_sign + exponent_sign + digits_dot + exponent_symbol + division_extra_space;

double original = -0.000013213213e-100/33215.;
oss << std::setprecision(prec) << std::setw(width) << original << std::setw(width) << original << std::setw(width) << original << '\n';
oss << std::setprecision(prec) << std::setw(width) << 1. << std::setw(width) << 2. << std::setw(width) << -3. << '\n';
}

打印

 -3.9780861056751466e-110 -3.9780861056751466e-110 -3.9780861056751466e-110
                        1                        2                       -3

总而言之,在我的情况下,它就像设置:

oss << std::precision(17) << std::setw(25) << original << ...;

在任何情况下,我都可以通过以下方式测试这是否有效:

    std::istringstream iss(oss.str());
    double test; iss >> test;
    assert(test == original);

【讨论】:

    【解决方案2】:

    如果你想保持格式严格的人类可读性,你可以这样写出双精度:

    #include <iomanip>
    #include <sstream>
    
    std::string doubleToText(const double & d)
    {
        std::stringstream ss;
        //ss << std::setprecision( std::numeric_limits<double>::digits10+2);
        ss << std::setprecision( std::numeric_limits<int>::max() );
        ss << d;
        return ss.str();
    }
    

    std::numeric_limits&lt;int&gt;::max() 将以可能的最大十进制精度输出。这将在不同的浮点实现中最精确地保留该值。使用std::numeric_limits&lt;double&gt;::digits10+2 将该行交换为注释行将提供足够的精度,以使双精度可以在编译代码的平台上精确恢复。这提供了更短的输出,并保留了 double 可以唯一表示的尽可能多的信息。

    在读入字符串时,C++ 流运算符不保留非规范化数字或无穷大和非数字。但是,POSIX strtod 函数保留,并由标准定义.因此,通过标准库调用读取十进制数的最精确方法是这个函数:

    #include <stdlib.h>
    
    double textToDouble(const std::string & str)
    {
        return strtod( str.c_str(), NULL );
    }
    

    【讨论】:

    • strtod 函数似乎是最轻松、最完整的解决方案。
    • std::numeric_limits&lt;double&gt;::max_digits10std::numeric_limits&lt;double&gt;::digits10+2 更标准
    【解决方案3】:

    一个两步过程:首先使用binary float/double serialization,然后应用base 64 encoding。结果不是人类可读的,但不会降低精度。

    编辑:(感谢fuzzyTew 和dan04)

    无损十进制和人类可读的表示可能是可能的,但需要更多的空间。

    【讨论】:

    • 当然可以创建一种人类可读的表示形式,能够准确地表示二进制浮点数。
    • 右:2 是 10 的因数,因此所有终止的二进制小数也以 10 为底。虽然它可能需要很多数字,例如 0.10000000000000000555511151231257827021181583404541015625。
    • 但是不可能将十进制浮点表示为二进制浮点。我说的对吗?
    • 一般来说,没有。二进制中的 1/5 是 0.0011 0011 0011 0011...,因此分母中因子为 5 的任何分数都不会以二进制结尾。
    【解决方案4】:

    假设 IEEE 754 双倍,printf("%.17g\n", x) 将为您提供足够的数字来重新创建原始值。

    【讨论】:

    • 解析它的最佳方法是什么?保留无穷大和可能是 NaN 的选项有哪些?
    • 您可以检测到所有特殊情况,例如在 cplusplus.com/forum/beginner/30400 中,但通常为什么需要呢?此外,十进制表示肯定很好且可读,但您必须记住 IEEE 双精度中的尾数和指数都是二进制的,因此很难保留所有位...可能您需要自己的 bin2dec,dec2bin 函数无论如何。
    • 该线程中有很多不良信息。除以零是在运行时完成的,并由 fpu 分配一个结果。
    • 这个想法是你可以像这样获得 +INF 和 -INF 的编码。否则,您只需要直接访问双精度位域,请参阅en.wikipedia.org/wiki/Double_precision_floating-point_format
    • 不起作用,带有大指数的非常小的数字不起作用。
    【解决方案5】:

    抛开存储表示,像这样的东西呢。 -0、无穷大、NaN 等特殊值需要特殊 虽然处理。我也“忘了”实现负指数。

    #include <stdio.h>
    #include <math.h>
    
    const int SCALE = 1<<(52/2);
    
    void put( double a ) {
      FILE* f = fopen( "dump.txt", "wb" );
      int sign = (a<0); if( sign ) a=-a;
      int exp2 = 0; while( a>1 ) a/=2, exp2++;
      a*=SCALE;
      int m1 = floor(a);
      a = (a-m1)*SCALE;
      int m2 = floor(a);
      fprintf(f, "%i %i %i %i\n", sign, exp2, m1, m2 );
      fclose(f);
    }
    
    double get( void ) {
      FILE* f = fopen( "dump.txt", "rb" );
      double a;
      int sign, exp2, m1, m2;
      fscanf( f, "%i %i %i %i\n", &sign, &exp2, &m1, &m2 );
      fclose(f);
      printf( "%i %i %i %i\n", sign, exp2, m1, m2 );
      a = m2; a /= SCALE;
      a+= m1; a /= SCALE;
      while( exp2>0 ) a*=2, exp2--;
      if( a<0 ) a=-a;
      return a;
    }
    
    int main( void ) {
      union {
        double a;
        unsigned b[2];
      };
      a = 3.1415926;
      printf( "%.20lf %08X %08X\n", a, b[0], b[1] );
      put( a );
      a = get();
      printf( "%.20lf %08X %08X\n", a, b[0], b[1] );
    }
    

    【讨论】:

      【解决方案6】:

      我确信printf(可能是%a?)有一个特殊的格式说明符,它允许打印浮点数的二进制表示,但我找不到它..
      不过,你可以试试这个:

      int main(int argc, char* argv[]){
          union fi {
              unsigned int i;
              float        f;
          } num;
          num.f = 1.23f;
          printf("%X\n", num.i);
          return 0;
      }
      

      【讨论】:

      • 没有帮助。整数或浮点表示都不能保证,因此转换为整数允许您打印数字,但不保证另一个系统将生成浮点值。 (另外你应该添加编译时检查浮点/整数是否相同)。
      • 您可以按照 ruslik 所说的做,并将输出定义为 IEEE 754。在任何不是这种情况的平台上,您都必须对双精度进行软件转换。
      • 当然,这段代码是用 c 而不是 c++(问题标签),并且适用于浮点数而不是双精度数——但它确实解决了问题
      【解决方案7】:

      你没有说为什么二进制是禁区。对于您的应用程序,将二进制转换为十六进制 ASCII 字符串是否可行?

      【讨论】:

      • 我唯一的限制是我必须输出到一个明文文件。用户需要访问文件中的其他列(使用 Excel、Matlab 等)。我想将这些数据放在同一个文件中,并编写可以复活二进制等效值的其他工具。
      【解决方案8】:

      您可以使用 base 64。这将允许您将确切的字节值存储在文本文件中。

      我没用过,但我找到了这个 base 64 编码/解码library for C++。

      【讨论】:

      • 除了与浮点无关。仅仅因为人们用它来编码二进制数据并不意味着你可以编码浮点数并期望它们正确地出现在另一端!!!
      • 由于大多数系统倾向于遵循 IEEE 754,因此您可以将浮点数很好地编码为二进制数据
      • @fuzzyTew:总有一个选择:要么牺牲便携性,要么牺牲精度 :)
      • @fuzzyTew:是的。但是,如果两个平台上的格式相同,则以全精度打印不会获得任何收益(如果打印出读入的确切值,如果两端的格式相同,则不会获得任何收益(您只会降低精度如果一端截断数据))。所以你牺牲了可移植性而一无所获(我想你获得了更好的压缩)。
      • Base 64 将始终使用 13 个字符来表示 64 位双精度,而以全精度打印相同的双精度可能需要数倍的时间,而且忘记在输出流比以 64 为基数写出 8 个字节。正如@fuzzyTew 所说,大多数现代系统都遵循 IEEE 754,因此可移植性可能没有那么多限制。
      【解决方案9】:

      试试这个:

      double d = 0.2512958125912;
      std::ostringstream s;
      s << d;
      

      然后将 s 写入文件。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-18
        相关资源
        最近更新 更多