Преобразование float в строку без потери точности

Я хочу сохранить значение float в строку без потери или добавления каких-либо отдельных цифр точности.

Например, если мое значение float равно 23.345466467, я хочу, чтобы моя строка имела точные цифры str = "23.345466467.

Я попытался использовать функцию формата CString с% f. Это дает только первые 6 точности. или если я использую% 10, если мое значение float имеет точность менее 10, это добавляет еще некоторую точность нежелательной почты. Я хочу получить точное значение float в моей строке. как это сделать?

10 ответов

В первую очередь: вы не можете преобразовать float в строку и обратно, не потеряв немного или два. Поскольку это не преобразование 1 к 1, поскольку они используют различную базу.

Для сохранения наилучшей точности для типа с плавающей запятой:

std::string convert(float value)
{
 std::stringstream ss;
 ss << std::setprecision(std::numeric_limits<float>::digits10+1);
 ss << value;
 return ss.str();
}
</float>

Или более общий

template<typename floatingpointtype="">
std::string convert(FloatingPointType value)
{
 std::stringstream ss;
 ss << std::setprecision(std::numeric_limits<floatingpointtype>::digits10+1);
 ss << value;
 return ss.str();
}
</floatingpointtype></typename>

Он отрезал бы цифры, которые вам не нужны, но с максимальной точностью.


Это будет зависеть от того, является ли ваше значение float 23.345466467 точно представимым (скорее всего, не)

Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой

Почему числа с плавающей запятой могут потерять точность

Я бы также спросил, почему вам нужно это делать? Для чего вы собираетесь использовать строковое представление? Знаете ли вы о двойном и десятичном типах?

[ Неподтвержденный: вы можете попробовать выполнить двойное нажатие, а затем использовать "% d". Может быть, это приведет к добавлению дополнительных "охранных" цифр, но все равно не будет работать для всех значений]


C99 поддерживает формат %a в printf, который позволяет выводить содержимое двойника без потери точности.


См. подробное обсуждение в http://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2/.

Короткий ответ заключается в том, что минимальная точность такова:

printf("%1.8e", d); // Round-trippable float, always with an exponent
printf("%.9g", d); // Round-trippable float, shortest possible
printf("%1.16e", d); // Round-trippable ******, always with an exponent
printf("%.17g", d); // Round-trippable ******, shortest possible

Или, что то же самое, с std::ostream& os:

os << scientific << setprecision(8) << d; // float; always with an exponent
os << defaultfloat << setprecision(9) << d; // float; shortest possible
os << scientific << setprecision(16) << d; // ******; always with an exponent
os << defaultfloat << setprecision(17) << d; // ******; shortest possible


Другие уже прокомментировали, что 23.345466467 не существует как float, но если ваша цель - это просто конвертирование значений с плавающей точкой в ​​оба конца, которые существуют, не изменяя при этом незначительное их значение, вы можете использовать snprintf и strtod с по меньшей мере DECIMAL_DIG местами (POSIX, но не простой ISO C, гарантирует точное округление) или распечатать float в шестнадцатеричном формате вместо десятичного. C99 имеет спецификатор формата %a для печати поплавков в шестнадцатеричном формате, но если вы не можете зависеть от C99, легко написать свой собственный. В отличие от десятичной, наивный алгоритм для печати шестнадцатеричных поплавков отлично работает. Он выглядит следующим образом:

  • Масштабировать по мощности 2, чтобы установить значение в диапазоне 0x8 <= val < 0x10. Эта сила 2 становится показателем в вашем результате.
  • Повторно удалите целочисленную часть val и умножьте на 0x10, чтобы получить выходные цифры.
  • Когда val достигнет 0, вы закончили.

Преобразование в противоположном направлении также легко.


Число (десятичных) цифр, которые вам нужно уникально, представляет любой float по определению std::numeric_limits<float>::digits10</float>. Формат float не может различать 0.0 и 0.0000, поэтому эта константа является максимальным значением, которое вам когда-либо понадобится.

Теперь обратите внимание, что я сказал уникально. Это не точное. Это означает, что если вы пишете два разных двоичных значения, вы получите два разных десятичных представления. Это также означает, что, если вы читаете такое десятичное представление, его нельзя путать с каким-либо другим значением, и поэтому вы возвращаете то же самое двоичное значение, которое у вас было до этого. Обычно эти две гарантии достаточны.


Лучший способ - сохранить его двоичное представление, как это было предложено в @pgm. Однако вы также можете сохранить его в десятичной нотации, но с использованием нотации периода, например:

23.5095446(1545855463)

Таким образом, они могут быть как-то "без потерь", но для этого потребуется очень тщательное кодирование.


Сохраняете ли вы его, чтобы вы могли перечитать его позже и использовать его как float? Или вам все равно, что говорит строка? Как говорили другие, вам нужно написать двоичное представление, а не base10. Или вы можете быть немного сложнее и сделать что-то вроде этого

float f=23.345466467
int i=*(int*)&f; //re-interpret the bits in f as an int
cout << i;

и прочитать его

int i;
cin >> i;
float f=*(float&)&i;

[Стандартный дискламер о переносимости и убедитесь, что int и float имеют одинаковый размер на вашей платформе.]

Таким образом вы можете вывести значение и прочитать его обратно без потери точности. Но он не читается человеком, когда он хранится.


Возможно, вы используете fprintf() (from) для преобразования вашего поплавка в строку. Но точность может быть потеряна сразу после аффектации, потому что "флеш" и "двойной" имеют ограниченную точность. Кто-то должен подтвердить это.


В С# используйте цифровую строку форматирования "R", как в:

float x = 23.345466467f;
string str = x.ToString("R");

Обратите внимание, что "R" не подходит для "Кругового путешествия". Дополнительная информация о MSDN.

licensed under cc by-sa 3.0 with attribution.