这个例程被称为无数次,用于创建大量数字的大型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)
}
答案 0 :(得分:10)
在开始之前,请检查此功能是否花费了大量时间。通过测量仪或其他方法进行测量。知道你称之为无数次是非常好的,但是如果事实证明你的程序仍然只花费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<double>::max_exponent10 + 14
之类的值(基本上, -DBL_MAX
需要的长度。麻烦的是,这会分配和存储比通常需要的内存更多的内存(IEEE双字节为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 :(得分:5)
在速度或简洁方面有效吗?
char buf[64];
sprintf(buf, "%-.*G", 16, 1.0);
cout << buf << endl;
显示“1”。在恢复科学记数法之前,格式为16位左右,没有尾随零。
答案 2 :(得分:1)
snprintf
和char
而不是stringstream
和string
char
缓冲区的指针传递给它打印到的dbl2str(以避免返回时调用的string
的复制构造函数)。将要打印的字符串汇编到字符缓冲区中(或在调用字符串时将char缓冲区转换为字符串或将其添加到现有字符串中)在头文件中声明函数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;
}