Float in String konvertieren mit Genauigkeit und Anzahl der angegebenen Dezimalstellen?

87

Wie konvertiert man einen Float in eine Zeichenfolge in C ++, während man die Genauigkeit und Anzahl der Dezimalstellen angibt?

Beispielsweise: 3.14159265359 -> "3.14"

Shannon Matthews
quelle
Warum nicht einen temporären Float mit der gewünschten Genauigkeit erstellen und ihn dann in einen String konvertieren?
Gilad
@Gilad Der 'temporäre Float' hat keine 'spezifizierte Genauigkeit' und es gibt Fälle, in denen ein solcher Ansatz zusammenbricht. Diese Frage ist im Grunde wie die Frage "Was entspricht dem Format '% .2f'"?
user2864740

Antworten:

130

Ein typischer Weg wäre zu verwenden stringstream:

#include <iomanip>
#include <sstream>

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

Siehe behoben

Verwenden Sie eine feste Gleitkommanotation

Setzt das floatfieldFormat-Flag für den str- Stream auf fixed.

Wenn diese floatfieldOption auf gesetzt ist fixed, werden Gleitkommawerte in Festkommanotation geschrieben: Der Wert wird mit genau so vielen Stellen im Dezimalteil dargestellt, wie im Genauigkeitsfeld ( precision) angegeben, und ohne Exponententeil.

und setprecision .


Für Konvertierungen von technischen Zwecken wie das Speichern von Daten in XML- oder JSON-Dateien definiert C ++ 17 die Funktionsfamilie to_chars .

Unter der Annahme eines kompatiblen Compilers (der uns zum Zeitpunkt des Schreibens fehlt) kann Folgendes in Betracht gezogen werden:

#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
quelle
28

Die übliche Methode, um so etwas zu tun, ist "Print to String". In C ++ bedeutet das std::stringstreamFolgendes:

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

Eine weitere Option ist snprintf:

double pi = 3.1415926;

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

Demo . Fehlerbehandlung sollte hinzugefügt werden, dh nach prüfenwritten < 0.

Columbo
quelle
Warum wäre snprintfes in diesem Fall besser? Bitte erklären Sie ... Es wird sicherlich nicht schneller sein, weniger Code produzieren [Ich habe eine printf-Implementierung geschrieben, es ist ziemlich kompakt mit knapp 2000 Codezeilen, aber mit Makroerweiterungen werden es ungefähr 2500 Zeilen]
Mats Petersson
1
@MatsPetersson Ich glaube fest daran, dass es schneller gehen wird. Das ist der Grund, warum ich diese Antwort überhaupt gegeben habe.
Columbo
@MatsPetersson Ich habe Messungen durchgeführt (GCC 4.8.1, -O2), die darauf hinweisen, dass snprintf tatsächlich um den Faktor 17/22 weniger Zeit benötigt. Ich werde den Code in Kürze hochladen.
Columbo
@MatsPetersson Mein Benchmark ist wahrscheinlich fehlerhaft, aber die Stringstream-Version dauert bei 1000000-Iterationen doppelt so lange wie die snprintf-Version (Clang 3.7.0, libstdc ++, -O2).
Ich habe auch einige Messungen durchgeführt - und es scheint (zumindest unter Linux), dass sowohl stringstream als auch snprintf dieselbe zugrunde liegende __printf_fpFunktionalität verwenden - es ist ziemlich seltsam, dass es so viel länger dauert stringstream, als es eigentlich nicht sein sollte.
Mats Petersson
8

Sie können die fmt::formatFunktion aus der {fmt} -Bibliothek verwenden :

#include <fmt/core.h>

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

Wo 2ist eine Präzision.

Diese Formatierungsfunktion wurde zur Standardisierung in C ++: P0645 vorgeschlagen . Sowohl P0645 als auch {fmt} verwenden eine Python-ähnliche String-Syntax, die der von printf's ähnelt, jedoch {}anstelle von Trennzeichen verwendet wird %.

vitaut
quelle
7

Hier eine Lösung mit nur std. Beachten Sie jedoch, dass dies nur abrundet.

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

Denn roundedes ergibt:

3.14

Der Code konvertiert den gesamten Float in einen String, schneidet jedoch alle Zeichen 2 Zeichen nach dem "."

Sebastian R.
quelle
Das einzige ist, dass Sie auf diese Weise die Zahl nicht wirklich runden.
ChrCury78
1
@ ChrCury78 wie in meiner Antwort angegeben, rundet dies nur ab. Wenn Sie eine normale Rundung wünschen, addieren Sie einfach 5 zum Diget nach Ihrem Wert. Zum Beispiel number + 0.005in meinem Fall.
Sebastian R.
2

Hier gebe ich ein negatives Beispiel, bei dem Sie vermeiden möchten, wenn Sie eine schwebende Zahl in Zeichenfolgen konvertieren.

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

Du erhältst

99463 99.463 99.462997

Hinweis: Die Variable num kann ein beliebiger Wert nahe 99,463 sein. Sie erhalten den gleichen Ausdruck. Der Punkt ist, die bequeme c ++ 11 "to_string" -Funktion zu vermeiden. Ich habe eine Weile gebraucht, um diese Falle zu überwinden. Der beste Weg ist die Stringstream- und Sprintf-Methode (C-Sprache). C ++ 11 oder neuer sollte einen zweiten Parameter als Anzahl der Stellen nach dem anzuzeigenden Gleitkomma bereitstellen. Im Moment ist die Standardeinstellung 6. Ich setze dies so, dass andere keine Zeit mit diesem Thema verschwenden.

Ich habe meine erste Version geschrieben. Bitte lassen Sie mich wissen, wenn Sie einen Fehler finden, der behoben werden muss. Sie können das genaue Verhalten mit dem Iomanipulator steuern. Meine Funktion besteht darin, die Anzahl der Stellen nach dem Dezimalpunkt anzuzeigen.

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
quelle