2013-03-01 8 views
8

Diese Routine wird eine Zillion mal aufgerufen, um große csv-Dateien voller Zahlen zu erstellen. Gibt es dafür einen effizienteren Weg?doppelt zu string ohne wissenschaftliche Notation oder nachfolgende Nullen, effizient

static std::string dbl2str(double d) 
    { 
     std::stringstream ss; 
     ss << std::fixed << std::setprecision(10) << d;    //convert double to string w fixed notation, hi precision 
     std::string s = ss.str();         //output to std::string 
     s.erase(s.find_last_not_of('0') + 1, std::string::npos);  //remove trailing 000s (123.1200 => 123.12, 123.000 => 123.) 
     return (s[s.size()-1] == '.') ? s.substr(0, s.size()-1) : s; //remove dangling decimal (123. => 123) 
    } 
+1

Der Titel scheint falsch, sollte doppelt zu string sein? – hyde

+0

oops - Titel ist rückwärts. . . natürlich ist es doppelt zu string – tpascale

+0

Mögliches Duplikat von [Formatierung n signifikante Ziffern in C++ ohne wissenschaftliche Notation] (http://stackoverflow.com/questions/17211122/formatting-n-significant-digits-in-c-without-scientific- Notation) – mirams

Antwort

7
in einer Header-Datei deklarieren

Bevor Sie beginnen, prüfen Sie, ob in dieser Funktion viel Zeit verbraucht wird. Tun Sie dies, indem Sie entweder mit einem Profiler oder auf andere Weise messen. Zu wissen, dass Sie es eine Zillion Mal nennen, ist alles sehr gut, aber wenn es sich herausstellt, dass Ihr Programm nur 1% seiner Zeit in dieser Funktion verbringt, dann kann nichts, was Sie hier tun, Ihre Programmleistung um mehr als 1% verbessern. Wenn das der Fall wäre, wäre die Antwort auf Ihre Frage "für Ihre Zwecke nein, diese Funktion kann nicht wesentlich effizienter gemacht werden und Sie verschwenden Ihre Zeit, wenn Sie es versuchen".

Zuerst, vermeiden Sie s.substr(0, s.size()-1). Dies kopiert die meisten der Zeichenfolge und es macht Ihre Funktion für NRVO nicht in Frage, so dass ich denke, in der Regel erhalten Sie eine Kopie bei der Rückkehr. So ist die erste Änderung, die ich machen würde, ist die letzte Zeile zu ersetzen:

if(s[s.size()-1] == '.') { 
    s.erase(s.end()-1); 
} 
return s; 

Aber wenn Leistung ein ernstes Problem ist, dann ist hier, wie ich es tun würde. Ich verspreche nicht, dass dies die schnellste Möglichkeit ist, aber es vermeidet einige Probleme mit unnötigen Zuweisungen und Kopieren. Jeder Ansatz, der stringstream einbezieht, wird eine Kopie vom Stringstream zum Ergebnis erfordern, also wollen wir eine Low-Level-Operation, snprintf.

static std::string dbl2str(double d) 
{ 
    size_t len = std::snprintf(0, 0, "%.10f", d); 
    std::string s(len+1, 0); 
    // technically non-portable, see below 
    std::snprintf(&s[0], len+1, "%.10f", d); 
    // remove nul terminator 
    s.pop_back(); 
    // remove trailing zeros 
    s.erase(s.find_last_not_of('0') + 1, std::string::npos); 
    // remove trailing point 
    if(s.back() == '.') { 
     s.pop_back(); 
    } 
    return s; 
} 

Der zweite Aufruf von snprintf nimmt an, dass std::string sequenzielle Speicher verwendet. Dies ist in C++ 11 garantiert. Es ist nicht in C++ 03 garantiert, aber gilt für alle aktiv gepflegten Implementierungen von std::string, die dem C++ - Komitee bekannt sind. Wenn Leistung wirklich wichtig ist, dann ist es vernünftig, diese nicht portable Annahme zu machen, da das direkte Schreiben in eine Zeichenkette später das Kopieren in eine Zeichenkette spart.

s.pop_back() ist die C++ 11 Art und Weise s.erase(s.end()-1) zu sagen, und s.back() ist s[s.size()-1]

Für eine andere möglich Verbesserung, Sie loswerden dem ersten Aufruf von snprintf bekommen konnte und stattdessen Größe Ihre s auf einen Wert wie std::numeric_limits<double>::max_exponent10 + 14 (im Grunde die Länge, die -DBL_MAX benötigt). Das Problem ist, dass dies viel mehr Speicher reserviert und löscht, als normalerweise benötigt wird (322 Bytes für ein IEEE-Double). Meine Intuition ist, dass dies langsamer sein wird als der erste Anruf an snprintf, ganz zu schweigen von verschwenderischem Speicher in dem Fall, in dem der String-Rückgabewert für eine Weile vom Anrufer gehalten wird. Aber du kannst es immer testen.

Alternativ std::max((int)std::log10(d), 0) + 14 berechnet eine relativ enge Obergrenze für die benötigte Größe und möglicherweise schneller als snprintf kann es genau berechnen.

Schließlich kann es sein, dass Sie die Leistung durch Ändern der Funktionsschnittstelle verbessern können. Zum Beispiel, anstatt eine neue Zeichenfolge zurückkehren könnten Sie vielleicht in einem String anhang vom Anrufer übergeben:

void append_dbl2str(std::string &s, double d) { 
    size_t len = std::snprintf(0, 0, "%.10f", d); 
    size_t oldsize = s.size(); 
    s.resize(oldsize + len + 1); 
    // technically non-portable 
    std::snprintf(&s[oldsize], len+1, "%.10f", d); 
    // remove nul terminator 
    s.pop_back(); 
    // remove trailing zeros 
    s.erase(s.find_last_not_of('0') + 1, std::string::npos); 
    // remove trailing point 
    if(s.back() == '.') { 
     s.pop_back(); 
    } 
} 

Dann kann der Anrufer reserve() vielen Platz, rufen Sie Ihre Funktion mehrmals (vermutlich mit anderer Zeichenfolge anhängt in zwischen), und schreiben Sie den resultierenden Datenblock in die Datei auf einmal, ohne andere Speicherzuweisung als die reserve. "Plenty" muss nicht die ganze Datei sein, es könnte eine Zeile oder ein "Absatz" gleichzeitig sein, aber alles, was eine Unmenge von Speicherzuweisungen vermeidet, ist ein potenzieller Leistungsschub.

+0

danke für diese sehr detaillierte Erklärung – tpascale

1
  • Verwendung snprintf und eine Anordnung von char anstelle von stringstream und string
  • passieren einen Zeiger auf char Puffer dbl2str, in denen er druckt (um den Copykonstruktor von string aufgerufen zu vermeiden, wenn die Rückkehr) . Bauen Sie die Zeichenfolge in einem Zeichenpuffer gedruckt werden (oder die char buffer konvertieren, wenn in einen String oder fügen Sie es zu einem vorhandenen String genannt)
  • die Funktion inline

    #include <cstdio> 
    inline void dbl2str(char *buffer, int bufsize, double d) 
    { 
        /** the caller must make sure that there is enough memory allocated for buffer */ 
        int len = snprintf(buffer, bufsize, "%lf", d); 
    
        /* len is the number of characters put into the buffer excluding the trailing \0 
        so buffer[len] is the \0 and buffer[len-1] is the last 'visible' character */ 
    
        while (len >= 1 && buffer[len-1] == '0') 
        --len; 
    
        /* terminate the string where the last '0' character was or overwrite the existing 
        0 if there was no '0' */ 
        buffer[len] = 0; 
    
        /* check for a trailing decimal point */ 
        if (len >= 1 && buffer[len-1] == '.') 
        buffer[len-1] = 0; 
    } 
    
+0

Schlüsselwort * inline * wirkt sich nicht direkt auf die Optimierung durch "Inlining" aus, es ist eine Anweisung für den Linker, dass dieses Symbol oft beim Verknüpfen erscheint und es kein Fehler ist. Die Funktion ist bereits * statisch * in Frage. – hyde

4

Effizient in Bezug auf Geschwindigkeit oder Kürze?

char buf[64]; 
sprintf(buf, "%-.*G", 16, 1.0); 
cout << buf << endl; 

Zeigt "1" an. Formatiert bis zu signifikanten 16 Ziffern ohne abschließende Nullen, bevor die wissenschaftliche Notation wiederhergestellt wird.

+0

Die - ist nicht unbedingt notwendig (es wird gerechtfertigt) –

Verwandte Themen