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