[英]double to string without scientific notation or trailing zeros, efficiently
这个例程被称为无数次,以创建充满数字的大型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%于此功能,那么您在此处不做任何事情可能会使程序的性能提高1%以上。 如果真是这样,那么您的问题的答案将是“出于您的目的,不能使此功能显着提高效率,并且如果您尝试浪费时间”。
首先,请避免s.substr(0, s.size()-1)
。 这将复制大多数字符串,并使您的函数不符合NRVO的条件,因此我认为通常您会在返回时获得一个副本。 因此,我要做的第一个更改是将最后一行替换为:
if(s[s.size()-1] == '.') {
s.erase(s.end()-1);
}
return s;
但是,如果性能是一个严重的问题,那么我将按照以下方法进行操作。 我不能保证这是最快的方法,但是可以避免不必要的分配和复制带来的一些问题。 任何涉及stringstream
方法都需要从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()
是s.erase(s.end()-1)
的C ++ 11表达方式,而s.back()
是s[s.size()-1]
对于另一个可能的改进,您可以摆脱对snprintf
的第一次调用,而是将s
大小调整为一些值,例如std::numeric_limits<double>::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
之外没有任何内存分配。 “大量”不必一定是整个文件,一次可以是一行或“一段”,但是避免任何数量的内存分配的任何事情都可能提高性能。
在速度或简洁性方面高效?
char buf[64];
sprintf(buf, "%-.*G", 16, 1.0);
cout << buf << endl;
显示“ 1”。 在还原为科学计数法之前,最多可格式化有效的16位数字,且不带尾随零。
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; }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.