Лучший способ вывести двойной точности с полной точностью в текстовый файл - PullRequest
19 голосов
/ 10 января 2011

Мне нужно использовать существующий текстовый файл для хранения некоторых очень точных значений.При обратном чтении числа по существу должны быть точно эквивалентны тем, которые были изначально написаны.Теперь обычный человек использовал бы двоичный файл ... по ряду причин, в данном случае это невозможно.

Итак ... у кого-нибудь из вас есть хороший способ кодирования двойного кода какстрока символов (кроме повышения точности).Моей первой мыслью было привести двойника к символу [] и выписать символы.Я не думаю, что это сработает, потому что некоторые символы не видны, производят звуки и даже заканчивают строки ('\ 0' ... Я разговариваю с вами!)

Мысли?

[Изменить] - как только я выясню, какое из предложенных решений лучше всего подходит для меня, я отмечу одно из них как «решение».

Ответы [ 9 ]

16 голосов
/ 12 января 2011

Если вы хотите сохранить формат, понятный человеку, вы можете записать двойное число таким образом:

#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.

Операторы потока C ++ не сохраняют денормализованные числа или бесконечности и не числа при чтении строк. Однако функция POSIX strtod выполняет и определяется стандартом. Следовательно, наиболее точным способом считывания десятичного числа обратно с помощью стандартного вызова библиотеки была бы эта функция:

#include <stdlib.h>

double textToDouble(const std::string & str)
{
    return strtod( str.c_str(), NULL );
}
9 голосов
/ 11 января 2011

Если IEEE 754 double, printf("%.17g\n", x) даст вам достаточно цифр для воссоздания исходного значения.

4 голосов
/ 10 января 2011

Двухэтапный процесс: сначала используйте двоичный код с плавающей запятой / двойную сериализацию , а затем примените кодировку base 64 .Результат не читается человеком, но не потеряет точность.

Редактировать: (Благодаря fuzzyTew и dan04)

Возможно десятичное представление без потерь и удобочитаемое человекомно потребует гораздо больше места.

2 голосов
/ 10 января 2011

Вы можете использовать базу 64. Это позволит вам хранить точные значения байтов в текстовом файле.

Я не использовал его, но нашел эту библиотеку кодирования / декодирования базы 64 библиотека для C ++.

1 голос
/ 18 декабря 2013

Для печати длинных списков чисел в C ++ без потерь (пишите и читайте в одной и той же архитектуре) Я использую это (для 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);
1 голос
/ 10 января 2011

Я был уверен, что существует специальный спецификатор формата для 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;
}
0 голосов
/ 10 января 2011

Хранение представления в стороне, что-то вроде этого. Специальные значения, такие как -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] );
}
0 голосов
/ 10 января 2011

Вы не говорите, почему двоичный код запрещен. Для вашего приложения будет ли возможно преобразование двоичного файла в шестнадцатеричную строку ASCII?

0 голосов
/ 10 января 2011

Попробуйте это:

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

Затем запишите s в файл.

...