From ac8dfd841f746f69a28ffc3e455aa4b0f4c37e9f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 29 May 2020 07:05:25 -0700 Subject: [PATCH] Improve handling of separators --- include/fmt/format.h | 128 ++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 74 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 095b7a71..17acde08 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -886,34 +886,31 @@ template <> inline wchar_t decimal_point(locale_ref loc) { return decimal_point_impl(loc); } -// Formats a decimal unsigned integer value writing into buffer. -// add_thousands_sep is called after writing each char to add a thousands -// separator if necessary. -template -inline Char* format_decimal(Char* buffer, UInt value, int num_digits, - F add_thousands_sep) { +template void copy2(Char* dst, const char* src) { + *dst++ = static_cast(*src++); + *dst = static_cast(*src); +} +inline void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } + +// Formats a decimal unsigned integer value writing into out. +template +inline Char* format_decimal(Char* out, UInt value, int num_digits) { FMT_ASSERT(num_digits >= 0, "invalid digit count"); - buffer += num_digits; - Char* end = buffer; + out += num_digits; + Char* end = out; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. - auto index = static_cast((value % 100) * 2); + out -= 2; + copy2(out, data::digits + static_cast((value % 100) * 2)); value /= 100; - *--buffer = static_cast(data::digits[index + 1]); - add_thousands_sep(buffer); - *--buffer = static_cast(data::digits[index]); - add_thousands_sep(buffer); } if (value < 10) { - *--buffer = static_cast('0' + value); + *--out = static_cast('0' + value); return end; } - auto index = static_cast(value * 2); - *--buffer = static_cast(data::digits[index + 1]); - add_thousands_sep(buffer); - *--buffer = static_cast(data::digits[index]); + copy2(out - 2, data::digits + static_cast(value * 2)); return end; } @@ -923,22 +920,15 @@ template constexpr int digits10() FMT_NOEXCEPT { template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } -template -inline Iterator format_decimal(Iterator out, UInt value, int num_digits, - F add_thousands_sep) { - FMT_ASSERT(num_digits >= 0, "invalid digit count"); +template +inline Iterator format_decimal(Iterator out, UInt value, int num_digits) { // Buffer should be large enough to hold all digits (<= digits10 + 1). enum { max_size = digits10() + 1 }; Char buffer[2 * max_size]; - auto end = format_decimal(buffer, value, num_digits, add_thousands_sep); + auto end = format_decimal(buffer, value, num_digits); return detail::copy_str(buffer, end, out); } -template -inline It format_decimal(It out, UInt value, int num_digits) { - return format_decimal(out, value, num_digits, [](Char*) {}); -} - template inline Char* format_uint(Char* buffer, UInt value, int num_digits, bool upper = false) { @@ -1412,6 +1402,16 @@ inline OutputIt write_padded(OutputIt out, return write_padded(out, specs, size, size, f); } +template +OutputIt write_bytes(OutputIt out, string_view bytes, + const basic_format_specs& specs) { + using iterator = remove_reference_t; + return write_padded(out, specs, bytes.size(), [bytes](iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); +} + // Data for write_int that doesn't depend on output iterator type. It is used to // avoid template code bloat. template struct write_int_data { @@ -1531,52 +1531,43 @@ template struct int_writer { enum { sep_size = 1 }; - struct num_writer { - UInt abs_value; - int size; - const std::string& groups; - Char sep; - - template It operator()(It it) const { - basic_string_view s(&sep, sep_size); - // Index of a decimal digit with the least significant digit having - // index 0. - int digit_index = 0; - std::string::const_iterator group = groups.cbegin(); - return format_decimal( - it, abs_value, size, [this, s, &group, &digit_index](Char*& buffer) { - if (*group <= 0 || ++digit_index % *group != 0 || - *group == max_value()) - return; - if (group + 1 != groups.cend()) { - digit_index = 0; - ++group; - } - buffer -= s.size(); - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(buffer, s.size())); - }); - } - }; - void on_num() { std::string groups = grouping(locale); if (groups.empty()) return on_dec(); auto sep = thousands_sep(locale); if (!sep) return on_dec(); int num_digits = count_digits(abs_value); - int size = num_digits; + int size = num_digits, n = num_digits; std::string::const_iterator group = groups.cbegin(); while (group != groups.cend() && num_digits > *group && *group > 0 && *group != max_value()) { size += sep_size; - num_digits -= *group; + n -= *group; ++group; } - if (group == groups.cend()) - size += sep_size * ((num_digits - 1) / groups.back()); - out = write_int(out, size, get_prefix(), specs, - num_writer{abs_value, size, groups, sep}); + if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); + char digits[40]; + format_decimal(digits, abs_value, num_digits); + memory_buffer buffer; + buffer.resize(size); + basic_string_view s(&sep, sep_size); + // Index of a decimal digit with the least significant digit having index 0. + int digit_index = 0; + group = groups.cbegin(); + auto p = buffer.data() + size; + for (int i = num_digits - 1; i >= 0; --i) { + *--p = digits[i]; + if (*group <= 0 || ++digit_index % *group != 0 || + *group == max_value()) + continue; + if (group + 1 != groups.cend()) { + digit_index = 0; + ++group; + } + p -= s.size(); + std::uninitialized_copy(s.data(), s.data() + s.size(), p); + } + write_bytes(out, {buffer.data(), buffer.size()}, specs); } void on_chr() { *out++ = static_cast(abs_value); } @@ -1586,16 +1577,6 @@ template struct int_writer { } }; -template -OutputIt write_bytes(OutputIt out, string_view bytes, - const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, bytes.size(), [bytes](iterator it) { - const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); - }); -} - template OutputIt write_nonfinite(OutputIt out, bool isinf, const basic_format_specs& specs, @@ -3175,8 +3156,7 @@ struct format_handler : detail::error_handler { basic_format_arg arg; }; -template -bool equal2(const Char* lhs, const char* rhs) { +template bool equal2(const Char* lhs, const char* rhs) { return lhs[0] == rhs[0] && lhs[1] == rhs[1]; } inline bool equal2(const char* lhs, const char* rhs) {