From 6ea6cf94646c740ccac8b4cb3782e7c573c83028 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 18 Jul 2021 07:39:22 -0700 Subject: [PATCH] Add decimal separator support to float --- include/fmt/format.h | 147 ++++++++++++++++++++++++++++--------------- test/xchar-test.cc | 4 +- 2 files changed, 101 insertions(+), 50 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 07d3524b..94f07195 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -150,10 +150,13 @@ FMT_END_NAMESPACE // __builtin_clz is broken in clang with Microsoft CodeGen: // https://github.com/fmtlib/fmt/issues/519 -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz) || FMT_ICC_VERSION) && !FMT_MSC_VER +#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz) || FMT_ICC_VERSION) && \ + !FMT_MSC_VER # define FMT_BUILTIN_CLZ(n) __builtin_clz(n) #endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll) || FMT_ICC_VERSION) && !FMT_MSC_VER +#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll) || \ + FMT_ICC_VERSION) && \ + !FMT_MSC_VER # define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) #endif #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz) || FMT_ICC_VERSION) @@ -970,7 +973,7 @@ template <> auto count_digits<4>(detail::fallback_uintptr n) -> int; FMT_INLINE auto do_count_digits(uint32_t n) -> int { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. -#define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) +# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 @@ -1408,41 +1411,62 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template class digit_grouping { private: thousands_sep_result sep_; - std::string::const_iterator group_; - int pos_; + + struct next_state { + std::string::const_iterator group; + int pos; + }; + next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + + // Returns the next digit group separator position. + int next(next_state& state) const { + if (!sep_.thousands_sep) return max_value(); + if (state.group == sep_.grouping.end()) + return state.pos += sep_.grouping.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } public: explicit digit_grouping(locale_ref loc) { - if (loc) { + if (loc) sep_ = thousands_sep(loc); - reset(); - } else { + else sep_.thousands_sep = Char(); - } - } - - void reset() { - group_ = sep_.grouping.begin(); - pos_ = 0; - } - - // Returns the next digit group separator position. - int next() { - if (!sep_.thousands_sep) return max_value(); - if (group_ == sep_.grouping.end()) return pos_ += sep_.grouping.back(); - if (*group_ <= 0 || *group_ == max_value()) return max_value(); - pos_ += *group_++; - return pos_; - } - - int count_separators(int num_digits) { - int count = 0; - while (num_digits > next()) ++count; - reset(); - return count; } Char separator() const { return sep_.thousands_sep; } + + int count_separators(int num_digits) const { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + Out apply(Out out, basic_string_view digits) const { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + *out++ = separator(); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } }; template @@ -1457,22 +1481,11 @@ auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, auto grouping = digit_grouping(loc); unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + grouping.count_separators(num_digits)); - auto buffer = basic_memory_buffer(); - buffer.resize(size); - int separator_pos = grouping.next(); - auto p = buffer.data() + size; - for (int i = 0; i < num_digits; ++i) { - if (i == separator_pos) { - *--p = grouping.separator(); - separator_pos = grouping.next(); - } - *--p = static_cast(digits[num_digits - i - 1]); - } - if (prefix != 0) *--p = static_cast(prefix); - out = write_padded(out, specs, size, size, - [=](reserve_iterator it) { - return copy_str(p, p + size, it); - }); + out = write_padded( + out, specs, size, size, [&](reserve_iterator it) { + if (prefix != 0) *it++ = static_cast(prefix); + return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); + }); return true; } @@ -1654,6 +1667,20 @@ inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { return format_decimal(out, significand, significand_size).end; } +template +inline auto write_significand(OutputIt out, T significand, int significand_size, + int exponent, + const digit_grouping& grouping) + -> OutputIt { + if (!grouping.separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} template ::value)> @@ -1696,6 +1723,24 @@ inline auto write_significand(OutputIt out, const char* significand, significand + significand_size, out); } +template +inline auto write_significand(OutputIt out, T significand, int significand_size, + int integral_size, Char decimal_point, + const digit_grouping& grouping) + -> OutputIt { + if (!grouping.separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(buffer_appender(buffer), significand, + significand_size, integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_str_noinline(buffer.data() + integral_size, + buffer.end(), out); +} + template auto write_float(OutputIt out, const DecimalFP& fp, const basic_format_specs& specs, float_specs fspecs, @@ -1761,10 +1806,12 @@ auto write_float(OutputIt out, const DecimalFP& fp, if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; } + auto grouping = digit_grouping(loc); + size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); - it = write_significand(it, significand, significand_size); - it = detail::fill_n(it, fp.exponent, zero); + it = write_significand(it, significand, significand_size, + fp.exponent, grouping); if (!fspecs.showpoint) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; @@ -1773,10 +1820,12 @@ auto write_float(OutputIt out, const DecimalFP& fp, // 1234e-2 -> 12.34[0+] int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); + auto grouping = digit_grouping(loc); + size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); it = write_significand(it, significand, significand_size, exp, - decimal_point); + decimal_point, grouping); return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 78ecb2c7..4e837aab 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -301,10 +301,12 @@ template struct small_grouping : std::numpunct { Char do_thousands_sep() const override { return ','; } }; -TEST(locale_test, double_decimal_point) { +TEST(locale_test, localized_double) { auto loc = std::locale(std::locale(), new numpunct()); EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23)); EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23)); + EXPECT_EQ("1~234?5", fmt::format(loc, "{:L}", 1234.5)); + EXPECT_EQ("12~000", fmt::format(loc, "{:L}", 12000.0)); } TEST(locale_test, format) {