From 6f0387fc227c186d06dfe1767439e0b091bbcb43 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 14 Feb 2014 10:36:17 -0800 Subject: [PATCH] Expose in-place decimal integer formatting via FormatDec. Improve performance of CountDigits based on the results from https://github.com/localvoid/cxx-benchmark-count-digits. --- format-test.cc | 14 ++++++++++++++ format.cc | 35 +++++++++++++++++++++++++++------ format.h | 52 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/format-test.cc b/format-test.cc index 9142c75b..6158b9d9 100644 --- a/format-test.cc +++ b/format-test.cc @@ -1422,6 +1422,20 @@ TEST(FormatIntTest, FormatInt) { EXPECT_EQ(os.str(), fmt::FormatInt(std::numeric_limits::max()).str()); } +TEST(FormatIntTest, FormatDec) { + char buffer[10]; + char *ptr = buffer; + fmt::FormatDec(ptr, 42); + EXPECT_EQ(buffer + 2, ptr); + *ptr = '\0'; + EXPECT_STREQ("42", buffer); + ptr = buffer; + fmt::FormatDec(ptr, -42); + *ptr = '\0'; + EXPECT_EQ(buffer + 3, ptr); + EXPECT_STREQ("-42", buffer); +} + template std::string str(const T &value) { return fmt::str(fmt::Format("{0}") << value); diff --git a/format.cc b/format.cc index e2083ecf..b9d4fcfb 100644 --- a/format.cc +++ b/format.cc @@ -116,6 +116,29 @@ const char fmt::internal::DIGITS[] = "4041424344454647484950515253545556575859" "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; + +const uint64_t fmt::internal::POWERS_OF_10[] = { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000u +}; void fmt::internal::ReportUnknownType(char code, const char *type) { if (std::isprint(static_cast(code))) { @@ -145,8 +168,8 @@ typename fmt::BasicWriter::CharPtr } template -void fmt::BasicWriter::FormatDecimal( - CharPtr buffer, uint64_t value, unsigned num_digits) { +void fmt::internal::FormatDecimal( + Char *buffer, uint64_t value, unsigned num_digits) { --num_digits; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead @@ -675,8 +698,8 @@ template fmt::BasicWriter::CharPtr fmt::BasicWriter::FillPadding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); -template void fmt::BasicWriter::FormatDecimal( - CharPtr buffer, uint64_t value, unsigned num_digits); +template void fmt::internal::FormatDecimal( + char *buffer, uint64_t value, unsigned num_digits); template fmt::BasicWriter::CharPtr fmt::BasicWriter::PrepareFilledBuffer( @@ -707,8 +730,8 @@ template fmt::BasicWriter::CharPtr fmt::BasicWriter::FillPadding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); -template void fmt::BasicWriter::FormatDecimal( - CharPtr buffer, uint64_t value, unsigned num_digits); +template void fmt::internal::FormatDecimal( + wchar_t *buffer, uint64_t value, unsigned num_digits); template fmt::BasicWriter::CharPtr fmt::BasicWriter::PrepareFilledBuffer( diff --git a/format.h b/format.h index 038901a6..a3d4f9dc 100644 --- a/format.h +++ b/format.h @@ -49,6 +49,7 @@ // Compatibility with compilers other than clang. #ifndef __has_feature # define __has_feature(x) 0 +# define __has_builtin(x) 0 #endif #ifndef FMT_USE_INITIALIZER_LIST @@ -171,8 +172,18 @@ void Array::append(const T *begin, const T *end) { template class CharTraits; +template +class BasicCharTraits { + public: +#if _SECURE_SCL + typedef stdext::checked_array_iterator CharPtr; +#else + typedef Char *CharPtr; +#endif +}; + template <> -class CharTraits { +class CharTraits : public BasicCharTraits { private: // Conversion from wchar_t to char is not supported. // TODO: rename to ConvertChar @@ -189,7 +200,7 @@ class CharTraits { }; template <> -class CharTraits { +class CharTraits : public BasicCharTraits { public: typedef const char *UnsupportedStrType; @@ -233,9 +244,17 @@ struct IsLongDouble { enum {VALUE = 1}; }; void ReportUnknownType(char code, const char *type); +extern const uint64_t POWERS_OF_10[]; + // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case CountDigits returns 1. inline unsigned CountDigits(uint64_t n) { +#if FMT_GCC_VERSION >= 400 or __has_builtin(__builtin_clzll) + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + uint64_t t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; + return t - (n < POWERS_OF_10[t]) + 1; +#else unsigned count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead @@ -248,12 +267,16 @@ inline unsigned CountDigits(uint64_t n) { n /= 10000u; count += 4; } +#endif } extern const char DIGITS[]; template class FormatterProxy; + +template +void FormatDecimal(Char *buffer, uint64_t value, unsigned num_digits); } /** @@ -581,17 +604,14 @@ class BasicWriter { friend class BasicFormatter; + typedef typename internal::CharTraits::CharPtr CharPtr; + #if _SECURE_SCL - typedef stdext::checked_array_iterator CharPtr; static Char *GetBase(CharPtr p) { return p.base(); } #else - typedef Char *CharPtr; static Char *GetBase(Char *p) { return p; } #endif - static void FormatDecimal( - CharPtr buffer, uint64_t value, unsigned num_digits); - static CharPtr FillPadding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); @@ -812,7 +832,7 @@ void BasicWriter::FormatInt(T value, const Spec &spec) { unsigned num_digits = internal::CountDigits(abs_value); CharPtr p = PrepareFilledBuffer(size + num_digits, spec, sign) + 1 - num_digits; - BasicWriter::FormatDecimal(p, abs_value, num_digits); + internal::FormatDecimal(GetBase(p), abs_value, num_digits); break; } case 'x': case 'X': { @@ -1316,6 +1336,22 @@ class FormatInt { std::size_t size() const { return buffer_ - str_ + BUFFER_SIZE - 1; } }; +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template +inline void FormatDec(char *&buffer, T value) { + typedef typename internal::IntTraits::UnsignedType UnsignedType; + UnsignedType abs_value = value; + if (internal::IntTraits::IsNegative(value)) { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + unsigned num_digits = internal::CountDigits(abs_value); + internal::FormatDecimal(buffer, abs_value, num_digits); + buffer += num_digits; +} + /** \rst Formats a string similarly to Python's `str.format