diff --git a/cppformat/format.h b/cppformat/format.h index 67f02f22..4e26d828 100644 --- a/cppformat/format.h +++ b/cppformat/format.h @@ -29,6 +29,7 @@ #define FMT_FORMAT_H_ #include +#include #include #include #include @@ -871,9 +872,38 @@ inline unsigned count_digits(uint32_t n) { } #endif +// A functor that doesn't add a thousands separator. +struct NoThousandsSep { + template + void operator()(Char *) {} +}; + +// A functor that adds a thousands separator. +class ThousandsSep { + private: + fmt::StringRef sep_; + + // Index of a decimal digit with the least significant digit having index 0. + unsigned digit_index_; + + public: + explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {} + + template + void operator()(Char *&buffer) { + if (++digit_index_ % 3 != 0) + return; + buffer -= sep_.size(); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), buffer); + } +}; + // Formats a decimal unsigned integer value writing into buffer. -template -inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { +// thousands_sep is a functor that is called after writing each char to +// add a thousands separator if necessary. +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, + ThousandsSep thousands_sep) { buffer += num_digits; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead @@ -882,7 +912,9 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { unsigned index = static_cast((value % 100) * 2); value /= 100; *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); *--buffer = Data::DIGITS[index]; + thousands_sep(buffer); } if (value < 10) { *--buffer = static_cast('0' + value); @@ -893,6 +925,11 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { *--buffer = Data::DIGITS[index]; } +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { + return format_decimal(buffer, value, num_digits, NoThousandsSep()); +} + #ifndef _WIN32 # define FMT_USE_WINDOWS_H 0 #elif !defined(FMT_USE_WINDOWS_H) @@ -2627,9 +2664,8 @@ void BasicWriter::write_int(T value, Spec spec) { switch (spec.type()) { case 0: case 'd': { unsigned num_digits = internal::count_digits(abs_value); - CharPtr p = prepare_int_buffer( - num_digits, spec, prefix, prefix_size) + 1 - num_digits; - internal::format_decimal(get(p), abs_value, num_digits); + CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0); break; } case 'x': case 'X': { @@ -2684,6 +2720,14 @@ void BasicWriter::write_int(T value, Spec spec) { } while ((n >>= 3) != 0); break; } + case 'n': { + unsigned num_digits = internal::count_digits(abs_value); + fmt::StringRef sep = std::localeconv()->thousands_sep; + std::size_t size = num_digits + sep.size() * (num_digits - 1) / 3; + CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep)); + break; + } default: internal::report_unknown_type( spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); diff --git a/test/format-test.cc b/test/format-test.cc index 01c06bbb..121a7be6 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1164,7 +1165,7 @@ TEST(FormatterTest, FormatShort) { TEST(FormatterTest, FormatInt) { EXPECT_THROW_MSG(format("{0:v", 42), FormatError, "missing '}' in format string"); - check_unknown_types(42, "bBdoxX", "integer"); + check_unknown_types(42, "bBdoxXn", "integer"); } TEST(FormatterTest, FormatBin) { @@ -1248,6 +1249,16 @@ TEST(FormatterTest, FormatOct) { EXPECT_EQ(buffer, format("{0:o}", ULONG_MAX)); } +TEST(FormatterTest, FormatIntLocale) { +#ifndef _WIN32 + const char *locale = "en_US.utf-8"; +#else + const char *locale = "English_United States"; +#endif + std::setlocale(LC_ALL, locale); + EXPECT_EQ("1,234,567", format("{:n}", 1234567)); +} + TEST(FormatterTest, FormatFloat) { EXPECT_EQ("392.500000", format("{0:f}", 392.5f)); } @@ -1311,7 +1322,7 @@ TEST(FormatterTest, FormatLongDouble) { } TEST(FormatterTest, FormatChar) { - const char types[] = "cbBdoxX"; + const char types[] = "cbBdoxXn"; check_unknown_types('a', types, "char"); EXPECT_EQ("a", format("{0}", 'a')); EXPECT_EQ("z", format("{0:c}", 'z'));