Performant way of formatting numbers according to the user's locale with iostream?

 ̄綄美尐妖づ 提交于 2019-12-23 03:39:35

问题


In one part of our application there is the requirement to format numbers according to the user's locale.

The interface essentially looks like this:

std::string format_number(double number);

The old implementation looked like this:

std::string format_number(double number) {
    using namespace std;
    static const locale user_loc("");
    ostringstream fmtstream;
    fmtstream.imbue(user_loc);
    fmtstream << std::fixed;
    fmtstream << number;
    return fmtstream.str();
}

We have now noticed that with our compiler (MSVC 2005), we can get a approx 10-20% speed up of this function (measured in isolation) by using num_put directly:

std::string format_number(double number) {
    using namespace std;
    static const locale user_loc("");
    ostringstream fmtstream;
    fmtstream.imbue(user_loc);
    fmtstream << std::fixed;
    typedef char* CharBufOutIt;
    typedef num_put<char, CharBufOutIt> np_t;
    np_t const& npf = use_facet<np_t>(user_loc);
    char buf[127];
    const CharBufOutIt begin = &buf[0];
    const CharBufOutIt end = npf.put(/*out=*/begin, /*format=*/fmtstream, /*fill=*/' ', number);
    return std::string(begin, end);
}

This still seems suboptimal:

  • We need to construct a stream object just to imbue it with the locale and use it for the formatting flags.
  • We cannot cache the stream object, as put will call width(0)on the passed format stream.

Is the another "trick" to speed this up, or is this the most we can hope to get out of iostream?

And note that we cannot use sprintf here, as we need to make sure the full locale, including thousands separator, is used, and printf doesn't output the thousand separator.


回答1:


You need the stream for formatting and the locale. If you needed neither of those I would've suggested using a default-constructed num_put facet directly. The only optimization I can see here is to use std::string iterators instead of the raw pointers:

typedef std::back_insert_iterator<std::string> iter_type;
typedef std::num_put<char, iter_type>          facet_type;

const facet_type& num_put = std::use_facet<facet_type>(user_loc);
std::string buf;

num_put.put(std::back_inserter(buf), fmtstream, fmtstream.fill(), number);

In response to the comments, here is how you would implement your function using a default-constructed facet type:

template<class Facet>
class erasable_facet : public Facet
{
public:
    erasable_facet() : Facet(1)
    { }

    ~erasable_facet() { }
};

std::string format_number(double number)
{
    typedef std::back_insert_iterator<std::string> iter_type;
    typedef std::num_put<char, iter_type>          facet_type;

    erasable_facet<facet_type> num_put;
    std::ios str(0);
    std::string buf;

    num_put.put(std::back_inserter(buf), str, str.fill(), number);
    return buf;
}



回答2:


Thanks to user 0x499602D2's answer I have been able to speed this up some more (even with a locale facet).

The point is to not use a stream for the formatting flags, since the /*format=*/ parameter to put is only accessed for it's flags and locale, so a more basic object suffices:

std::string version_3(double number) {
    using namespace std;
    static const locale user_loc("");
    ios fmtios(NULL); // instead of a full stream
    fmtios.imbue(user_loc);
    fmtios.setf(ios_base::fixed);
    typedef char* CharBufOutIt;
    typedef num_put<char, CharBufOutIt> np_t;
    np_t const& npf = use_facet<np_t>(user_loc);
    char buf[127];
    const CharBufOutIt begin = &buf[0];
    const CharBufOutIt end = npf.put(/*out=*/begin, /*format=*/fmtios, /*fill=*/' ', value);
    return std::string(begin, end);
}

I prematurely had dismissed this version because I was not aware that you can actually legally pass a nullptr as stream buffer to basic_ios. The docs for init show you can, although it will result in badbit being set, but as far as I'm able to tell put doesn't care about the state bits.



来源:https://stackoverflow.com/questions/20384848/performant-way-of-formatting-numbers-according-to-the-users-locale-with-iostrea

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!