开发者

best way to output a full precision double into a text file

I need to use an existing text file to store some very precise values. When read back in, the numbers essentially need to be exactly equivalent to the ones that were originally written. Now, a normal person would use a binary file... for a number of reasons, that's not possible in this case.

So... do any of you have a good way of encoding a double as a string of characters (aside from increasing the precision). My first thought was to cast the double to a char[] and write out the chars. I don't think that's going to work because some of the characters are not visible, produce sounds, and even terminate strings ('\0'... I开发者_如何学Python'm talkin to you!)

Thoughts?

[Edit] - once I figure out which of the solutions proposed works best for me, I'll mark one as 'the' solution.


If you want to keep the format strictly human readable, you can write out the double thusly:

#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() will output with the maximum possible decimal precision. This will preserve the value most precisely across differently floating point implementations. Swapping that line for the commented line using std::numeric_limits<double>::digits10+2 will give just enough precision to make the double precisely recoverable on the platform the code is compiled for. This gives a much shorter output and preserves as much information as the double can uniquely represent.

The C++ stream operators do not preserve denormalized numbers or the infinities and not-a-numbers when reading strings in. However, the POSIX strtod function does, and is defined to by the standard. Hence, the most precise way to read a decimal number back with a standard library call would be this function:

#include <stdlib.h>

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


Assuming IEEE 754 double, printf("%.17g\n", x) will give you enough digits to recreate the original value.


A two step process: First use binary float/double serialization and then apply base 64 encoding. The result is not human readable, but will not loose precision.

Edit: (Thanks to fuzzyTew and dan04)

Lossless decimal and human readable representation is probably possible, but would require much more space.


You could use base 64. This would allow you to store the exact byte values in a text file.

I haven't used it, but I found this base 64 encoding/decoding library for C++.


To print long lists of numbers in C++ without loss (write and read in the same arquitecture) I use this (for 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';
}

prints

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

In summary, in my case it is like setting:

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

In any case I can test if this works, by doing:

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


I was sure there was a special format specifier for printf (maybe %a?) that allowed printing the binary representation of a float, but I cannot find it..
However, you can try this:

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


Try this:

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

Then write s to file.


You don't say why binary is off limits. For your application would conqverting the binary to a hex ASCII string be workable?


Storage representation aside, what about something like this. Special values like -0, infinities, NaN etc would require special handling though. Also I "forgot" to implement negative exponents.

#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] );
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜