将完整精度double输出到文本文件的最佳方法

时间:2011-01-10 04:17:32

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

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

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

思想?

[编辑] - 一旦我弄清楚哪个解决方案最适合我,我会将其标记为''解决方案。

9 个答案:

答案 0 :(得分:16)

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

#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<int>::max()将以最大可能的小数精度输出。这将在不同的浮点实现中最精确地保留值。使用std::numeric_limits<double>::digits10+2交换注释行的那一行将提供足够的精度,使得在编译代码的平台上可以精确地恢复double。这样可以提供更短的输出并保留尽可能多的信息,就像double可以唯一表示的那样。

C ++流运算符在读取字符串时不保留非规范化数字或无穷大而不是数字。但是,POSIX strtod函数执行,并由标准。因此,使用标准库调用读回十进制数的最精确方法是此函数:

#include <stdlib.h>

double textToDouble(const std::string & str)
{
    return strtod( str.c_str(), NULL );
}

答案 1 :(得分:9)

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

答案 2 :(得分:4)

两步流程:首先使用binary float/double serialization,然后应用base 64 encoding。结果不是人类可读的,但不会失去精确度。

编辑:(感谢fuzzyTew和dan04)

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

答案 3 :(得分:2)

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

我没有使用它,但我发现这个基本64位编码/解码library用于C ++。

答案 4 :(得分:1)

我确信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;
}

答案 5 :(得分:1)

用C ++打印很长的数字列表而不会丢失(在相同的arquitecture中写入和读取)我使用它(对于double s):

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

答案 6 :(得分:0)

试试这个:

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

然后将s写入文件。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

除了存储表示,这样的事情怎么样。 像-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] );
}