Подтвердить что ты не робот

Точное преобразование строк ↔ с плавающей запятой

Я ищу библиотечную функцию для преобразования чисел с плавающей запятой в строки и обратно в С++. Свойства, которые я хочу, это 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 гарантирована для каких-либо конкретных свойств, которые я требую, и подозреваю, что она ломается для многих чисел.

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

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

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

  • Как точно читать числа с плавающей запятой, Will Klinger
  • Как правильно печатать числа с плавающей запятой, Guy Steele et al.

Любая библиотека/функция С++, основанная на них, будет подходящей.

EDIT: я полностью отдаю себе отчет в том, что числа с плавающей запятой являются неточными представлениями десятичных чисел и что 1.34 == 1.3400000000000001. Однако, поскольку упомянутые выше работы указывают, что никаких оправданий для выбора отображения "1.3400000000000001"

EDIT2: В этой статье объясняется, что именно я ищу: http://drj11.wordpress.com/2007/07/03/python-poor-printing-of-floating-point/

4b9b3361

Ответ 1

Я думаю, что это делает то, что вы хотите, в сочетании со стандартной библиотекой 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. Предположительно, короткая версия медленнее или менее корректна или что-то еще? Было бы неутешительно, если бы не было....

Ответ 2

Я все еще не могу найти библиотеку, которая предоставляет необходимый код, но я нашел код, который действительно работает:

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

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

Ответ 3

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

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

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

Вот реализация num2str и str2num с двумя разными контекстами from_double (преобразование double → string → double) и from_string (строка преобразования → double → 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;
}

Ответ 4

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

Ответ 5

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

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

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