double to string без научной нотации или конечных нулей, эффективно

эта процедура называется zillion раз для создания больших 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)
    }

3 ответов


прежде чем начать, проверьте, тратится ли значительное время в этой функции. Сделайте это путем измерять, или с профилировщиком или в противном случае. Зная, что вы называете это миллиард раз, все очень хорошо, но если окажется, что ваша программа все еще тратит только 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, но верно для всех активно поддерживаемых реализаций std::string известно комитету C++. Если производительность действительно важна, то я думаю, разумно сделать это непереносимое предположение, так как запись непосредственно в строку сохраняет копирование в строку позже.

s.pop_back() это способ C++11 сказать s.erase(s.end()-1) и s.back() is s[s.size()-1]

еще возможно улучшение, вы можете избавиться от первого вызова snprintf и вместо размера s к некоторому значению, как std::numeric_limits<double>::max_exponent10 + 14 (в основном, длина, что -DBL_MAX нужды). Беда в том, что это выделяет и нулей гораздо больше памяти, чем обычно требуется (322 байта для двойника IEEE). Моя интуиция подсказывает, что это будет медленнее, чем первый вызов 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 
         so buffer[len] is the  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;
    }