Konwertować zmiennoprzecinkowe na ciąg z określoną dokładnością i liczbą cyfr dziesiętnych?

88

Jak przekonwertować liczbę zmiennoprzecinkową na ciąg znaków w C ++, określając dokładność i liczbę cyfr dziesiętnych?

Na przykład: 3.14159265359 -> "3.14"

Shannon Matthews
źródło
dlaczego nie utworzyć tymczasowej zmiennej zmiennoprzecinkowej z określoną precyzją, a następnie przekonwertować ją na ciąg?
Gilad
@Gilad „Temp float” nie ma „określonej precyzji” i są przypadki, w których takie podejście się nie powiedzie. To pytanie jest w zasadzie jak pytanie „co jest odpowiednikiem formatu '% .2f'”?
user2864740
1
Zobacz stackoverflow.com/questions/14432043/c-float-formatting, jeśli jest to potrzebne tylko dla IO.
user2864740

Odpowiedzi:

131

Typowym sposobem byłoby użycie stringstream:

#include <iomanip>
#include <sstream>

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

Zobacz naprawione

Użyj stałej notacji zmiennoprzecinkowej

Ustawia floatfieldflagę formatu dla strumienia str na fixed.

Gdy floatfieldjest ustawiona na fixed, wartości zmiennoprzecinkowe są zapisywane przy użyciu notacji stałoprzecinkowej: wartość jest reprezentowana przez dokładnie tyle cyfr w części dziesiętnej, ile określono w polu precyzji ( precision) i bez części wykładniczej.

i setprecision .


Do konwersji celów technicznych , takich jak przechowywanie danych w pliku XML lub JSON, C ++ 17 definiuje rodzinę funkcji to_chars .

Zakładając zgodny kompilator (którego nam brakuje w momencie pisania), można rozważyć coś takiego:

#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
}
AlexD
źródło
28

Zwyczajową metodą robienia tego typu rzeczy jest drukowanie na łańcuchu. W C ++ oznacza to użycie std::stringstreamczegoś takiego jak:

std::stringstream ss;
ss << std::fixed << std::setprecision(2) << number;
std::string mystring = ss.str();
Mats Petersson
źródło
12

Inną opcją jest snprintf:

double pi = 3.1415926;

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

Demo . Należy dodać obsługę błędów, tj. Sprawdzaniewritten < 0.

Columbo
źródło
Dlaczego miałoby snprintfbyć lepiej w tym przypadku? Proszę wyjaśnić ... To na pewno nie będzie szybsze, wyprodukuje mniej kodu [Napisałem implementację printf, jest dość kompaktowa i zawiera prawie 2000 linii kodu, ale z rozszerzeniami makr dochodzi do około 2500 linii]
Mats Petersson
1
@MatsPetersson Mocno wierzę, że będzie szybciej. To jest powód, dla którego udzieliłem tej odpowiedzi w pierwszej kolejności.
Columbo
@MatsPetersson Przeprowadziłem pomiary (GCC 4.8.1, -O2), które wskazują, że snprintf rzeczywiście zajmuje mniej czasu, współczynnik 17/22. Wkrótce prześlę kod.
Columbo
@MatsPetersson Mój test porównawczy jest prawdopodobnie wadliwy, ale wersja stringstream zajmuje dwa razy więcej czasu niż wersja snprintf przy 1000000 iteracjach (Clang 3.7.0, libstdc ++, -O2).
Zrobiłem też kilka pomiarów - i wydawałoby się (przynajmniej w Linuksie), że zarówno stringstream, jak i snprintf używają tej samej podstawowej __printf_fpfunkcjonalności - jest raczej dziwne, że zajmuje to dużo więcej czasu stringstream, ponieważ naprawdę nie powinno.
Mats Petersson
9

Możesz użyć fmt::formatfunkcji z biblioteki {fmt} :

#include <fmt/core.h>

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

gdzie 2jest precyzja.

Ta funkcja formatowania została zaproponowana do standaryzacji w C ++: P0645 . Zarówno P0645, jak i {fmt} używają składni łańcucha formatu podobnej do Pythona, która jest podobna do printf's, ale {}zamiast tego używa jako separatorów %.

vitaut
źródło
7

Tutaj rozwiązanie wykorzystujące tylko standard. Należy jednak pamiętać, że to tylko zaokrągla w dół.

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

Za roundedto daje:

3.14

Kod konwertuje cały float na łańcuch, ale wycina wszystkie znaki 2 znaki po „.”

Sebastian R.
źródło
Tyle tylko, że w ten sposób nie zaokrągla się liczby.
ChrCury78
1
@ ChrCury78, jak stwierdzono w mojej odpowiedzi, zaokrągla to tylko w dół. Jeśli chcesz normalnego zaokrąglenia, po prostu dodaj 5 do cyfry po wartości. Na przykład number + 0.005w moim przypadku.
Sebastian R.
2

Tutaj przedstawiam negatywny przykład, w którym chcesz uniknąć konwersji liczby zmiennoprzecinkowej na ciągi.

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

Dostajesz

99463 99.463 99.462997

Uwaga: zmienna num może mieć dowolną wartość bliską 99.463, otrzymasz taki sam wydruk. Chodzi o to, aby uniknąć wygodnej funkcji „to_string” w języku c ++ 11. Chwilę zajęło mi wydostanie się z tej pułapki. Najlepszym sposobem są metody stringstream i sprintf (język C). C ++ 11 lub nowszy powinien zawierać drugi parametr jako liczbę cyfr po zmiennoprzecinkowym do pokazania. W tej chwili wartością domyślną jest 6. Stawiam na to, aby inni nie tracili czasu na ten temat.

Napisałem swoją pierwszą wersję, daj mi znać, jeśli znajdziesz jakiś błąd, który należy naprawić. Możesz kontrolować dokładne zachowanie za pomocą iomanipulatora. Moja funkcja służy do wyświetlania liczby cyfr po przecinku.

string ftos(float f, int nd) {
   ostringstream ostr;
   int tens = stoi("1" + string(nd, '0'));
   ostr << round(f*tens)/tens;
   return ostr.str();
}
Kemin Zhou
źródło