【问题标题】:double to string without scientific notation or trailing zeros, efficiently没有科学记数法或尾随零的双倍字符串,有效
【发布时间】:2013-03-01 19:37:18
【问题描述】:

这个例程被调用了无数次来创建充满数字的大型 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)
    }

【问题讨论】:

标签: c++ stringstream


【解决方案1】:

在开始之前,请检查此功能是否花费了大量时间。通过使用分析器或其他方式进行测量来做到这一点。知道你把它称为无数次是很好的,但是如果你的程序仍然只在这个函数上花费了 1% 的时间,那么你在这里所做的任何事情都不可能使你的程序性能提高 1% 以上。如果是这种情况,您的问题的答案将是“出于您的目的,否,此功能无法显着提高效率,如果您尝试,您就是在浪费时间”。

首先,避免s.substr(0, s.size()-1)。这会复制大部分字符串 并且 它使您的函数不符合 NRVO 的条件,因此我认为通常您会在返回时获得一份副本。所以我要做的第一个改变是将最后一行替换为:

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

但如果性能是一个严重的问题,那么我会这样做。我不保证这是最快的,但它避免了一些不必要的分配和复制问题。任何涉及stringstream 的方法都需要从字符串流复制到结果,因此我们需要更底层的操作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;
}

snprintf 的第二次调用假定std::string 使用连续存储。这在 C++11 中得到保证。在 C++03 中不能保证,但对于 C++ 委员会已知的所有积极维护的 std::string 实现都是如此。如果性能真的很重要,那么我认为做出这种不可移植的假设是合理的,因为直接写入字符串可以节省以后复制到字符串的操作。

s.pop_back() 是 C++11 的表达方式s.erase(s.end()-1),而s.back()s[s.size()-1]

对于另一个可能的改进,您可以摆脱对snprintf 的第一次调用,而是将您的s 调整为std::numeric_limits&lt;double&gt;::max_exponent10 + 14 之类的值(基本上,-DBL_MAX 的长度需要)。问题是这分配和归零的内存比通常需要的多得多(IEEE double 为 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”不必是整个文件,一次可以是一行或“段落”,但任何避免大量内存分配的东西都是潜在的性能提升。

【讨论】:

    【解决方案2】:

    在速度或简洁方面效率高?

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

    显示“1”。在恢复为科学记数法之前,最多可格式化 16 位有效数字,不带尾随零。

    【讨论】:

    • - 不是绝对必要的(左证明)
    【解决方案3】:
    • 使用snprintfchar 的数组代替stringstreamstring
    • 将指向char 缓冲区的指针传递给它打印到的dbl2str(以避免在返回时调用string 的复制构造函数)。将要打印的字符串组装到字符缓冲区中(或在调用时将字符缓冲区转换为字符串或将其添加到现有字符串中)
    • 在头文件中声明函数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;
      }
      

    【讨论】:

    • 关键字inline不直接影响“内联”的优化,它是链接器的指令,这个符号在链接中可能会出现多次,这不是错误。该函数已经静态有问题。
    猜你喜欢
    • 2017-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多