C++ double korrekt passend in std::string wandeln

T_55

Lieutenant
Registriert
Feb. 2013
Beiträge
638
Hallo,

ich wandle per std::to_string(doublewert) ein Doublewert in ein String um und danach soll es in ein File.
Zwei Probleme habe ich jetzt mit der Funktion festgestellt:

1.
Der String beinhaltet überflüssige Nullen nach dem Komma.
Aus einem double 0.1 wird ein "0.100000" string.
Da das String dann in eine File kommt verschwendet 0.100000 natürlich mehr Speicherplatz als 0.1 das ist nicht "optimal".

2.
Ein double Wert wie 0.998000012 wird durch std::to_string(doublewert) zu einem string "0.998000" das ist gar nicht gut.

to_string scheint alles auf 6 Digits zu pressen.

Daher die Frage wie kann man aus double Werten strings erstellen, die wirklich dazu passen also:
1. keine überflüssigen Nullen in den Nachkommastellen und
2. keine Nachkommastellen abschneiden

Gibt es da einen guten Trick oder muss man sich was kompliziertes bauen?

Grüße
 
Zu 1) Wenn dein Programm nicht zufällig auf einem Taschenrechner oder in einer Apollo-Mondkapsel laufen soll, würde ich das so lassen. Ja, es ist nicht optimal, aber wie heißt es so schön: "Premature optimization is the root of all evil."

Zu 2) https://www.google.de/search?q=c+++double+to+string+precision
 
Bin da leider zu perfektionistisch ;) Da es ein logfile ist, hat es auch wirklich ein gewissen Einfluss auf den Speicherplatz.

Aber das Fragen hat schon beim Denken geholfen :king:, habe was gemacht das funktioniert und löst beide Probleme perfekt, vielleicht ist es hilfreich für den ein oder anderen:

Code:
std::string double_to_string(double &variable)
{
   std::ostringstream Str;
   Str.precision(15);
   Str << variable;
   return(Str.str());
}

:)
 
Zuletzt bearbeitet: (Code verkürzt)
Sieht gut aus.

Zum Perfektionismus: Das Problem an vorzeitigem Optimieren ist ja, dass man sich von wirklich wichtigen Sachen ablenkt und mit verfrühten "Optimierungen" den Code soweit verkompliziert, dass das ganze Programm am Ende unter mehr Problemen leidet. Wenn ein "perfektes" Programm geschrieben werden soll, dann heißt das zuallererst einfacher, korrekter, gut wartbarer Code. Wenn das Programm steht, analysiert man es und optimiert an den Stellen, wo es nötig ist und bis die gewünschte Performance erreicht ist.

Beispiel bei dir: Wenn das kein Log gewesen wäre, sondern bspw. eine Konfigurationsdatei (was vorher niemand wusste außer dir), kann es bei unterschiedlich langen Float-Strings wieder zu Problemen beim Einlesen kommen. Eine Bugquelle mehr halt. Und wie so oft, führt eine richtige Architektur (Präzision allgemein festlegen können) dazu, dass so kleine Probleme sich mitlösen.
Wenn du wirklich den Speicherplatz klein halten willst, würde ich noch eine ZIP-Blibliothek einbinden und das Log regelmäßig komprimieren. Damit sparst du _viel_ mehr Platz.
 
Das stimmt schon man muss immer aufpassen das eigentliche Ziel nicht zu vergessen bei solchen "Kleinigkeiten" mit denen man sich schön die Zeit vertreiben kann. Mit einer Zip-Bib zu komprimieren ist auch keine schlechte Idee.

Noch ein kleines Update:

Mit ist noch aufgefallen, dass mit der obigen Funktion eine 0.0001 als 1e-05 ausgegeben wird. Irgendwie scheint die default-Einstellung etwas zu "scientific" zu sein. Daher habe ich per std::fixed dieses Problem gelöst. Das fixed hat aber wieder dazu geführt, dass überflüssige Nullen entstanden sind. Die Nullen hab ich dann abgeschnitten. Dies wiederum führt dazu, dass auch mal ein Punkt nach einer gerade Zahl stehen bleibt ("1.000000" wird zu "1.") daher noch den Punkt entfernen wenn dieser existiert.

Also sehr passend zum Thema wie man mit "Kleinigkeiten" Zeit verbringen kann... :)
Die Funktion ist dadurch natürlich nicht kleiner geworden aber tut nun wirklich exakt was sie soll. Hier nochmal der aktuelle Stand:

Code:
std::string double_to_string(double &variable)
{
   std::ostringstream Str;
   Str.precision(15);
   Str << std::fixed << variable;
   std::string s = Str.str();
   s.erase(s.find_last_not_of('0')+1,std::string::npos);
   if(s.back() == '.') {
       s.pop_back();
   }
   return(s);
}
 
Eine kleine (perfektionistische) Anmerkung:

​Call by reference gibt erst grob ab 64 Byte einen Geschwindigkeitsvorteil, in deinem Fall verhindert er aber ein Aufruf a la "double_to_string(3.14)" (cannot convert const double to double&"). Daher entweder "const double&" oder einfach "double".

​return braucht keine Klammern (ist keine Funktion).

​Als Lösung wäre (sn)printf ein Ausweg (siehe https://stackoverflow.com/questions...-maintain-precision-of-floating-point-value):
Code:
std::string to_string(double a){
char buf[32]={};// Maximum is 18 with 64 bit double
auto count=snprintf_s(buf, 32, "%.17g", a);
return std::string(buf,count);
}
 
Danke, const hab ich noch eingepflegt.

Der Alternativ-Code per snprintf_s bringt bei mir ungenaue Ausgaben:
(zum Kompilieren musste snprintf_s noch nach _snprintf_s verändert werden)

1.1100001 double
wird zu
1.1100000999999999 string

oder

0.9 double
wird zu
0.90000000000000002 string

Insofern bleibe ich erstmal bei beim Code von #5 das klappt bisher sehr zuverlässig
 
T_55 schrieb:
Danke, const hab ich noch eingepflegt.

Der Alternativ-Code per snprintf_s bringt bei mir ungenaue Ausgaben:
(zum Kompilieren musste snprintf_s noch nach _snprintf_s verändert werden)

1.1100001 double
wird zu
1.1100000999999999 string

oder

0.9 double
wird zu
0.90000000000000002 string
Das liegt aber daran, das du diese Werte gar nicht exakt als double darstellen kannst, genauso wie du 1/3 nicht exakt als Dezimalzahl mit endlicher Stellenzahl darstellen kannst.
 
Ja allerdings wird in dem Fall mit Str.precision(15) dafür gesorgt, dass die Darstellung des strings bei geraden Zahlen viel besser aussieht und weniger Platz verbraucht. Wenn ich precision auf 20 stelle wird der Gleitkomma-Effekt auch dort im String sichtbar daher ist 15 in dem Fall gut. Bei 1/3 wird natürlich bei beiden Varianten die ganze verfügbare Stringlänge verbraten. Je nach Anwendungsfall kann man die precision möglichst weit runterstellen damit der string "sauberer" aussieht. Bei mir ist es am Ende hauptsächlich eine Sache der Lesbarkeit meiner Logfile und damit auch weniger Speicherverbrauch.
 
Ein interessantes Problem! Ich sehe eine Lösungsmöglichkeit indem man die letzte Nachkommastelle rundet. Dann würde z.B. aus den String 0.90000000000000002 -> 0.9000000000000000 und aus 1.1100000999999999 -> 1.1100001000000000. Die Nullen nach dem Komma von Hinten gesehen, sind dann über und das Ergebnis ist das "Erwartete" 0.9 und 0.1100001. Mit der programmtechnische Umsetzung kann ad hoc leider nicht dienen, bin aber definitiv auch daran interessiert. Runden nätürlich nur für eine gewissen Genauigkeit.
 
Zuletzt bearbeitet: (Inhalt ergänzt)
Du musst ein Kompromiss finden, zuerst sagst du, es muss exakt abgespeichert werden, das geht nur mit mindestens 17 Stellen, jetzt willst du es kompakt haben, das geht in 7 Stellen (-[0-9]e-123). Für mich als Physiker ist es genau genug, für Zahlen um 1 gibt es sogar Nachkommastellen :-).

​Ich persönlich habe lieber eine gleich lange Ausgabe von Zahlen, da ich sonst gerne mal im Komma "verrutsche".

​Hast du tatsächlich Probleme mit der Präzision bei std::to_string?
 
Mit Runden erreichst du zwar, das die Ausgabe exakt aussieht. Aber Weiterrechnen wirst du immer mit einem "falschen" Wert, weil ein double diese Dezimalzahlen nicht exakt darstellen kann.
 
" Also sehr passend zum Thema wie man mit "Kleinigkeiten" Zeit verbringen kann... "
Siehe Beitrag #2.. da wurde es ja schon gesagt. Ein typisches Beispiel für Optimierung wo man keine braucht und daher wertvolle Zeit verschwendet.
Mal davon abgesehen: Gerade in Logfiles finde ich gleich lange Darstellungen (6 Stellen hört sich doch gut an?) sehr gut, damit die Ausgaben 'aligned' sind.. wer will schon von Zeile zu Zeile auch noch nach links und rechts springen. Suche ich nachträglich in zB 1000 Zeilen einen auffälligen Wert geht das aligned viel einfacher. Oder machs direkt als CSV mit Trennzeichen für weitere Verarbeitung - dann ist die Zahl der Stellen eh unerheblich.
 
Tja, Gleitkommaarithmetik hat seine Tücken z.B. ist nicht assoziativ, die Lücken zwischen den Zahlen nehmen mit deren Größe usw. Von daher wird man idR. mit "falschen" Werten (=Annäherungen) rechnen. Finde es von daher nicht schlecht, wenn die ausgegebenen Werten zumindest für Menschen besser lesbar sind, auch wenn das Minimal zu Lasten der Genauigkeit geht /(gehen könnte).
 
Zuletzt bearbeitet: (Ergänzung)
am einfachsten wäre es - glaube ich - folgendermaße:
Code:
...
double Wert = 0.998000012;
doubleToString(Wert);
...
std::String doubleToString(wert )
{
retturn "" + wert + "";
}
lg
Tron36
 

Ähnliche Themen

2
Antworten
28
Aufrufe
7.286
Zurück
Oben