2013-03-01 10 views
8

このルーチンは数字でいっぱいの大きなCSVファイルを作成するのに何百回も呼ばれます。これにもっと効率的な方法がありますか?科学的な表記や末尾のゼロのない文字列に二重に、効率的に

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

タイトルが間違っているようですが、文字列に2倍にする必要がありますか? – hyde

+0

oops - タイトルは後方です。 。 。もちろんそれは文字列の2倍です – tpascale

+0

[科学的表記なしでC++でn桁の有効数字を書式設定する]可能な複製(http://stackoverflow.com/questions/17211122/formatting-n-significant-digits-in-c-without-scientific-表記) – mirams

答えて

7
に機能 inlineを宣言する文字バッファに印刷される文字列をアセンブル(または文字列に呼び出されたときに文字バッファを変換するか、既存の文字列に追加)

開始する前に、この機能でかなりの時間が費やされているかどうかを確認してください。プロファイラーなどで測定してください。何百回も呼び出すことが分かっていることは分かっていますが、プログラムがまだこの関数の1%を費やしているだけであれば、ここで何もプログラムのパフォーマンスを1%以上向上させることはできません。そうした場合、あなたの質問に対する答えは「あなたの目的のためではなく、この機能を大幅に効率化することはできず、試してみるとあなたの時間を無駄にしています。

まずはs.substr(0, s.size()-1)を避けてください。これにより、ほとんどの文字列がコピーされますので、あなたの機能はNRVOに適格ではありませんので、通常は返品時にコピーを受け取ると思います。だから私は作ると思います最初の変更はで最後の行を置き換えることです:

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

しかし、パフォーマンスが深刻な懸念がある場合は、ここで私はそれを行うだろう方法です。私はこれが可能な限り速いと約束しているわけではありませんが、不要な割り当てやコピーの問題を避けています。 stringstreamを含むどのようなアプローチでも、ストリングストリームから結果へのコピーが必要になるため、より低レベルの操作であるsnprintfが必要です。 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; 
} 

第二の呼び出しはstd::stringが連続記憶域を使用することを想定しています。これはC++ 11で保証されています。これはC++ 03では保証されていませんが、C++委員会に知られている、アクティブに維持されているすべての実装であるstd::stringに当てはまります。パフォーマンスが本当に重要なのであれば、文字列に直接書き込むと後で文字列にコピーを保存するので、移植性のない仮説を立てることは合理的だと思います。

s.pop_back()s.erase(s.end()-1)を言うのC++ 11の方法である、とs.back()はあなたのsのようないくつかの値にサイズの代わりにsnprintfに最初の呼び出しを取り除くとができ、別の可能向上のためs[s.size()-1]

ですstd::numeric_limits<double>::max_exponent10 + 14(基本的には、-DBL_MAXの長さが必要です)。問題は、これにより、通常必要とされるよりもはるかに多くのメモリが割り当てられ、0になります(IEEEの2倍の場合は322バイト)。私の直感は、文字列の戻り値が呼び出し側によってしばらくぶら下がっている場合にメモリが無駄になることは言うまでもなく、これがsnprintfへの最初の呼び出しよりも遅くなるということです。しかし、いつでもテストすることができます。

また、std::max((int)std::log10(d), 0) + 14は、必要なサイズの合理的にタイトな上限を計算し、snprintfより正確に計算できる場合があります。

最後に、機能インターフェイスを変更してパフォーマンスを向上させることができます。たとえば、代わりに新しい文字列を返すのあなたは、おそらく、呼び出し側によって渡された文字列に追加することができます:

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(); 
    } 
} 

その後、発信者は宇宙のreserve()たくさんのことができ、あなたの関数を呼び出す数回は(おそらく他の文字列として追加します)、そしてreserve以外のメモリ割り当てをせずに、結果のデータブロックを一度にファイルに書き込むことができます。 "Plenty"はファイル全体である必要はなく、一度に1行または「段落」になる可能性がありますが、膨大なメモリ割り当てを避けることはパフォーマンスの向上につながります。

+0

この非常に詳細な説明のおかげで – tpascale

1
  • 使用snprintf、代わりstringstreamstring
  • charの配列は、それが(復帰したときに呼び出さstringのコピーコンストラクタを回避するために)印刷その中にdbl2strするcharバッファへのポインタを渡します。
  • ヘッダファイル

    #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

キーワード*インライン*はインライン展開による最適化に直接影響しません。リンク時にこのシンボルが何回も表示される可能性があり、エラーではありません。この関数はすでに* static *です。 – hyde

4

スピードや簡潔さに関して効率的ですか?

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

「1」と表示されます。科学的表記法に戻る前に、末尾のゼロのない最大16桁までの書式を設定します。

+0

- 厳密には必要ではありません(それは正当化されます) –

関連する問題