Точное преобразование строки с плавающей точкой <->

Я ищу библиотечную функцию для преобразования чисел с плавающей запятой в строки и обратно, в C ++. Мне нужны следующие свойства: str2num (num2str (x)) == x и num2str (str2num (x)) == x (насколько это возможно). Общее свойство заключается в том, что num2str должен представлять простейшее рациональное число, которое при округлении до ближайшего представимого числа с плавающим указателем возвращает вам исходное число.

До сих пор я пробовал boost :: lexical_cast:

double d = 1.34;
string_t s = boost::lexical_cast<string_t>(d);
printf("%s\n", s.c_str());
// outputs 1.3400000000000001

И я попробовал std :: ostringstream, который, кажется, работает для большинства значений, если я делаю stream.precision (16). Тем не менее, с точностью 15 или 17 он либо усекает, либо выдает ужасный результат для таких вещей, как 1,34. Я не думаю, что точность 16 гарантированно будет иметь какие-то особые свойства, которые мне требуются, и подозреваю, что она ломается для многих чисел.

Есть ли библиотека C ++, которая имеет такое преобразование? Или такая функция преобразования уже похоронена где-то в стандартных библиотеках / boost.

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

Я знаю, что функции чтения / показа на Haskell уже имеют свойства, которые мне нужны, как и библиотеки BSD C. Стандартные ссылки для строковых <-> двойных преобразований - это пара статей из PLDI 1990:

Как правильно читать числа с плавающей запятой, Уилл КлингерКак правильно печатать числа с плавающей запятой, Гай Стил и др.

Подойдет любая библиотека / функция C ++, основанная на них.

РЕДАКТИРОВАТЬ: Я полностью осознаю, что числа с плавающей запятой являются неточными представлениями десятичных чисел, и что 1.34 == 1.3400000000000001. Тем не менее, как указано в приведенных выше документах, это не оправдывает выбор отображения «1.3400000000000001»

РЕДАКТИРОВАТЬ 2: Эта статья объясняет, что именно я ищу:http://drj11.wordpress.com/2007/07/03/python-poor-printing-of-floating-point/

 greyfade21 авг. 2009 г., 22:10
Вы можете посмотреть на GMP и MPFR для программной эмуляции с плавающей точкой. Но то, что вы просите, почти невозможно с C ++float а такжеdouble типы.
 Neil Mitchell21 авг. 2009 г., 13:10
Библиотека C одинаково хороша - сейчас я просто проверяю документы NAG.
 AProgrammer21 авг. 2009 г., 12:52
Когда я искал один, я нашел один в C - не в C ++. У меня нет ссылки здесь. Кажется, я помню, что это было на ftp-сайте NAG, но я могу ошибаться.

Ответы на вопрос(5)

которая предоставляет необходимый код, но я нашел какой-то код, который работает:

http://svn.python.org/view/python/branches/py3k/Python/dtoa.c?view=markup

Предоставляя довольно небольшое количество определений, легко абстрагироваться от интеграции Python. Этот код действительно соответствует всем описанным мною свойствам.

это артефакт того, как они хранят значение.

Что вы действительно ищете, так это представление десятичного числа. В основном это использует целое число для хранения числа и имеет определенную точность после десятичной точки.

Быстрый Google получил это:http://www.codeproject.com/KB/mcpp/decimalclass.aspx

 Neil Mitchell21 авг. 2009 г., 13:16
На самом деле я предпочитаю рациональные числа:haskell.org/ghc/docs/latest/html/libraries/base/Data-Ratio.html - они имеют гораздо большую представительную власть.
 Neil Mitchell21 авг. 2009 г., 13:14
В общем, десятичные числа, конечно, предпочтительнее, но в этом случае я действительно хочу использовать числа с плавающей запятой из-за других ограничений в системе.
 Neil Mitchell12 сент. 2009 г., 18:37
Обычные ограничения: большая существующая кодовая база, проблемы с производительностью, ограничения архитектуры и т. Д.
 Martin York21 авг. 2009 г., 18:09
Ага. Я видел это как способ представления арифметики произвольной точности (но это не то, что вы просили). Вы хотите (ed) метод для представления значения с плавающей точкой произвольной точности (аналогично, но не одинаково). Итак, ЧТО являются не упомянутыми ограничениями, которые заставляют вас использовать значение точки потока.

я думаю, вы обнаружите, что 1.34 - это 1.3400000000000001. Числа с плавающей точкой не точны. Вы не можете обойти это. Например, 1.34f - 1.34000000333786011.

 Neil Mitchell12 сент. 2009 г., 18:38
Точно, так почему бы не показать более короткий, когда дан 1.34f? Это все, что я прошу :-)

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

Вы не можете иметь преобразование double → string → double и в то же время иметь читаемую строку.

Вам нужно выбрать между точным преобразованием и удобочитаемой строкой. Это определениеmax_digits10 а такжеdigits10:

Разница объясняется переполнением стекаdigits10max_digits10

Вот реализацияnum2str а такжеstr2num с двумя разными контекстамиfrom_double (преобразование double → string → double) иfrom_string (строка преобразования → двойная → строка):

#include <iostream>
#include <limits>
#include <iomanip>
#include <sstream>

namespace from_double
{
  std::string num2str(double d)
  {
    std::stringstream ss;
    ss << std::setprecision(std::numeric_limits<double>::max_digits10) << d;
    return ss.str();
  }

  double str2num(const std::string& s)
  {
    double d;
    std::stringstream ss(s);
    ss >> std::setprecision(std::numeric_limits<double>::max_digits10) >> d;
    return d;
  }
}

namespace from_string
{
  std::string num2str(double d)
  {
    std::stringstream ss;
    ss << std::setprecision(std::numeric_limits<double>::digits10) << d;
    return ss.str();
  }

  double str2num(const std::string& s)
  {
    double d;
    std::stringstream ss(s);
    ss >> std::setprecision(std::numeric_limits<double>::digits10) >> d;
    return d;
  }
}

int main()
{
  double d = 1.34;
  if (from_double::str2num(from_double::num2str(d)) == d)
    std::cout << "Good for double -> string -> double" << std::endl;
  else
    std::cout << "Bad for double -> string -> double" << std::endl;

  std::string s = "1.34";
  if (from_string::num2str(from_string::str2num(s)) == s)
    std::cout << "Good for string -> double -> string" << std::endl;
  else
    std::cout << "Bad for string -> double -> string" << std::endl;

  return 0;
}
Решение Вопроса

что это делает то, что вы хотите, в сочетании со стандартной библиотекой strtod ():

#include <stdio.h>
#include <stdlib.h>

int dtostr(char* buf, size_t size, double n)
{
  int prec = 15;
  while(1)
  {
    int ret = snprintf(buf, size, "%.*g", prec, n);
    if(prec++ == 18 || n == strtod(buf, 0)) return ret;
  }
}

Простая демонстрация, в которой не нужно проверять входные слова на наличие завершающего мусора:

int main(int argc, char** argv)
{
  int i;
  for(i = 1; i < argc; i++)
  {
    char buf[32];
    dtostr(buf, sizeof(buf), strtod(argv[i], 0));
    printf("%s\n", buf);
  }
  return 0;
}

Некоторые примеры входов:

% ./a.out 0.1 1234567890.1234567890 17 1e99 1.34 0.000001 0 -0 +INF NaN
0.1
1234567890.1234567
17
1e+99
1.34
1e-06
0
-0
inf
nan

Я полагаю, что ваша библиотека C должна соответствовать какой-то достаточно новой версии стандарта, чтобы гарантировать правильное округление.

Я не уверен, что выбрал идеальные границыprecНо я думаю, что они должны быть рядом. Может быть, они могут быть крепче? Точно так же я думаю, что 32 символа дляbuf всегда достаточно, но никогда не нужно. Очевидно, что все это предполагает 64-битные двойные значения IEEE. Возможно, стоит проверить это предположение с помощью какой-то хитрой директивы препроцессора -sizeof(double) == 8 было бы хорошим началом.

Экспонента немного запутана, но это не составит труда исправить после выхода из цикла, но перед возвратом, возможно, используяmemmove() или тому подобное, чтобы сдвинуть вещи влево. Я уверен, что гарантированно будет максимум один+ и максимум один ведущий0и я не думаю, что они могут даже произойти одновременно дляprec >= 10 или так.

Аналогично, если вы предпочитаете игнорировать ноль со знаком, как это делает Javascript, вы можете легко справиться с этим заранее, например:

if(n == 0) return snprintf(buf, size, "0");

Мне было бы любопытно увидеть подробное сравнение с этим чудовищем из 3000 строк, которое вы выкопали в базе кода Python. Предположительно короткая версия медленнее, или менее правильная, или что-то? Было бы неутешительно, если бы это было ни ....

 Neil Mitchell18 мар. 2014 г., 19:00
Я исследовал набор тестов. Результаты на VS2008 вашей версии также хороши, но не идентичны - например, первый алгоритм предпочитает 87.21565540666982, в то время как ваш предпочитает 87.21565540666983, но оба имеют одинаковое представление битов. Ваш алгоритм также на 3% медленнее. Но, учитывая тысячи строк уродливого C против вашего довольно элегантного ответа, вы определенно выиграете :).

Ваш ответ на вопрос