Перетворити float у рядок із заданою точністю та кількістю десяткових цифр?


88

Як перетворити поплавок у рядок на C ++, одночасно вказавши точність і кількість десяткових цифр?

Наприклад: 3.14159265359 -> "3.14"


чому б не створити тимчасовий поплавок із заданою точністю, яку ви хочете, а потім перетворити його на рядок?
Гілад

@Gilad 'Temp float' не має 'заданої точності', і бувають випадки, коли такий підхід зламається. Це питання в основному схоже на запитання "що є еквівалентом формату '% .2f'"?
користувач2864740

1
Дивіться stackoverflow.com/questions/14432043/c-float-formatting, якщо це просто потрібно для введення-виведення.
user2864740

Відповіді:


131

Типовим способом є використання stringstream:

#include <iomanip>
#include <sstream>

double pi = 3.14159265359;
std::stringstream stream;
stream << std::fixed << std::setprecision(2) << pi;
std::string s = stream.str();

Дивіться виправлене

Використовуйте фіксовану нотацію з плаваючою комою

Встановлює floatfieldпрапор формату для потоку strfixed .

Коли floatfieldвстановлено значення fixed, значення з плаваючою точкою записуються з використанням нотації з фіксованою крапкою: значення представляється рівно стільки цифр у десятковій частині, скільки вказано полем точності ( precision), і без експоненти.

і точність встановлення .


Для перетворень технічного призначення , таких як зберігання даних у файлі XML або JSON, C ++ 17 визначає сімейство функцій to_chars .

Якщо припустити сумісний компілятор (якого нам не вистачає на момент написання статті), можна розглянути щось подібне:

#include <array>
#include <charconv>

double pi = 3.14159265359;
std::array<char, 128> buffer;
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), pi,
                               std::chars_format::fixed, 2);
if (ec == std::errc{}) {
    std::string s(buffer.data(), ptr);
    // ....
}
else {
    // error handling
}

28

Звичайним методом такого роду є "друк у рядок". У C ++ це означає використання std::stringstreamчогось на зразок:

std::stringstream ss;
ss << std::fixed << std::setprecision(2) << number;
std::string mystring = ss.str();

12

Інший варіант snprintf:

double pi = 3.1415926;

std::string s(16, '\0');
auto written = std::snprintf(&s[0], s.size(), "%.2f", pi);
s.resize(written);

Демо . Слід додати обробку помилок, тобто перевірку на наявністьwritten < 0.


Чому snprintfв цьому випадку було б краще? Будь ласка , поясніть ... Це , звичайно , не буде швидким, виробляють менше коду [Я написав реалізація Printf, це досить компактний трохи менш як 2000 рядків коду, але з макро - розширень отримує близько 2500 рядків]
Матс Петерсона

1
@MatsPetersson Я впевнений, що це буде швидше. Ось чому я зробив цю відповідь.
Коламбо

@MatsPetersson Я проводив вимірювання (GCC 4.8.1, -O2), які вказують на те, що snprintf дійсно займає менше часу, з коефіцієнтом 17/22. Я завантажу код незабаром.
Коламбо

@MatsPetersson Мій бенчмарк, мабуть, помилковий, але версія рядкового потоку займає вдвічі більше часу, ніж версія snprintf на ітераціях 1000000 (Clang 3.7.0, libstdc ++, -O2).

Я теж зробив кілька вимірювань - і здавалося б (принаймні в Linux), що і streamstream, і snprintf використовують одну і ту ж базову __printf_fpфункціональність - досить дивно, що це займає набагато більше часу stringstream, оскільки цього насправді не повинно бути.
Mats Petersson

9

Ви можете використовувати fmt::formatфункцію з бібліотеки {fmt} :

#include <fmt/core.h>

int main()
  std::string s = fmt::format("{:.2f}", 3.14159265359); // s == "3.14"
}

де 2точність.

Цей засіб форматування запропоновано для стандартизації на мові C ++: P0645 . І P0645, і {fmt} використовують Python-подібний синтаксис рядкового формату, подібний до синтаксису printf'', але використовуючи {}як роздільники замість %.


7

Тут рішення використовує лише std. Однак зауважте, що це лише округлюється.

    float number = 3.14159;
    std::string num_text = std::to_string(number);
    std::string rounded = num_text.substr(0, num_text.find(".")+3);

Бо roundedце дає:

3.14

Код перетворює весь поплавок у рядок, але вирізає всі символи на 2 символи після "."


Єдине, що таким чином ви фактично не округляєте число.
ChrCury78

1
@ ChrCury78, як зазначено у моїй відповіді, це лише заокруглює. Якщо ви хочете звичайне округлення, просто додайте 5 до цифри відповідно до вашого значення. Наприклад number + 0.005у моєму випадку.
Себастьян Р.

2

Тут я наводжу негативний приклад, коли ви хочете уникати при перетворенні плаваючого числа в рядки.

float num=99.463;
float tmp1=round(num*1000);
float tmp2=tmp1/1000;
cout << tmp1 << " " << tmp2 << " " << to_string(tmp2) << endl;

Ти отримуєш

99463 99.463 99.462997

Примітка: змінною num може бути будь-яке значення, близьке до 99.463, ви отримаєте такий самий роздрук. Суть полягає в тому, щоб уникнути зручної функції c ++ 11 "to_string". Мені знадобився час, щоб вибратися з цієї пастки. Найкращий спосіб - це stringstream і sprintf методи (мова C). С ++ 11 або новіша версія повинна вказувати другий параметр як кількість цифр після плаваючої крапки для показу. Зараз за замовчуванням - 6. Я ставлю це, щоб інші не витрачали час на цю тему.

Я написав свою першу версію. Будь ласка, повідомте мене, якщо знайдете помилку, яку потрібно виправити. Ви можете контролювати точну поведінку за допомогою іоманіпулятора. Моя функція - показувати кількість цифр після десяткової коми.

string ftos(float f, int nd) {
   ostringstream ostr;
   int tens = stoi("1" + string(nd, '0'));
   ostr << round(f*tens)/tens;
   return ostr.str();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.