Implement locale-specific integer formatting

This commit is contained in:
vitaut 2016-04-17 19:06:03 -07:00
parent 581afee039
commit f68771abe4
2 changed files with 62 additions and 7 deletions

View File

@ -29,6 +29,7 @@
#define FMT_FORMAT_H_ #define FMT_FORMAT_H_
#include <cassert> #include <cassert>
#include <clocale>
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
@ -871,9 +872,38 @@ inline unsigned count_digits(uint32_t n) {
} }
#endif #endif
// A functor that doesn't add a thousands separator.
struct NoThousandsSep {
template <typename Char>
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 <typename Char>
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. // Formats a decimal unsigned integer value writing into buffer.
template <typename UInt, typename Char> // thousands_sep is a functor that is called after writing each char to
inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { // add a thousands separator if necessary.
template <typename UInt, typename Char, typename ThousandsSep>
inline void format_decimal(Char *buffer, UInt value, unsigned num_digits,
ThousandsSep thousands_sep) {
buffer += num_digits; buffer += num_digits;
while (value >= 100) { while (value >= 100) {
// Integer division is slow so do it for a group of two digits instead // 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<unsigned>((value % 100) * 2); unsigned index = static_cast<unsigned>((value % 100) * 2);
value /= 100; value /= 100;
*--buffer = Data::DIGITS[index + 1]; *--buffer = Data::DIGITS[index + 1];
thousands_sep(buffer);
*--buffer = Data::DIGITS[index]; *--buffer = Data::DIGITS[index];
thousands_sep(buffer);
} }
if (value < 10) { if (value < 10) {
*--buffer = static_cast<char>('0' + value); *--buffer = static_cast<char>('0' + value);
@ -893,6 +925,11 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {
*--buffer = Data::DIGITS[index]; *--buffer = Data::DIGITS[index];
} }
template <typename UInt, typename Char>
inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {
return format_decimal(buffer, value, num_digits, NoThousandsSep());
}
#ifndef _WIN32 #ifndef _WIN32
# define FMT_USE_WINDOWS_H 0 # define FMT_USE_WINDOWS_H 0
#elif !defined(FMT_USE_WINDOWS_H) #elif !defined(FMT_USE_WINDOWS_H)
@ -2627,9 +2664,8 @@ void BasicWriter<Char>::write_int(T value, Spec spec) {
switch (spec.type()) { switch (spec.type()) {
case 0: case 'd': { case 0: case 'd': {
unsigned num_digits = internal::count_digits(abs_value); unsigned num_digits = internal::count_digits(abs_value);
CharPtr p = prepare_int_buffer( CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1;
num_digits, spec, prefix, prefix_size) + 1 - num_digits; internal::format_decimal(get(p), abs_value, 0);
internal::format_decimal(get(p), abs_value, num_digits);
break; break;
} }
case 'x': case 'X': { case 'x': case 'X': {
@ -2684,6 +2720,14 @@ void BasicWriter<Char>::write_int(T value, Spec spec) {
} while ((n >>= 3) != 0); } while ((n >>= 3) != 0);
break; 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: default:
internal::report_unknown_type( internal::report_unknown_type(
spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer");

View File

@ -28,6 +28,7 @@
#include <cctype> #include <cctype>
#include <cfloat> #include <cfloat>
#include <climits> #include <climits>
#include <clocale>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
@ -1164,7 +1165,7 @@ TEST(FormatterTest, FormatShort) {
TEST(FormatterTest, FormatInt) { TEST(FormatterTest, FormatInt) {
EXPECT_THROW_MSG(format("{0:v", 42), EXPECT_THROW_MSG(format("{0:v", 42),
FormatError, "missing '}' in format string"); FormatError, "missing '}' in format string");
check_unknown_types(42, "bBdoxX", "integer"); check_unknown_types(42, "bBdoxXn", "integer");
} }
TEST(FormatterTest, FormatBin) { TEST(FormatterTest, FormatBin) {
@ -1248,6 +1249,16 @@ TEST(FormatterTest, FormatOct) {
EXPECT_EQ(buffer, format("{0:o}", ULONG_MAX)); 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) { TEST(FormatterTest, FormatFloat) {
EXPECT_EQ("392.500000", format("{0:f}", 392.5f)); EXPECT_EQ("392.500000", format("{0:f}", 392.5f));
} }
@ -1311,7 +1322,7 @@ TEST(FormatterTest, FormatLongDouble) {
} }
TEST(FormatterTest, FormatChar) { TEST(FormatterTest, FormatChar) {
const char types[] = "cbBdoxX"; const char types[] = "cbBdoxXn";
check_unknown_types('a', types, "char"); check_unknown_types('a', types, "char");
EXPECT_EQ("a", format("{0}", 'a')); EXPECT_EQ("a", format("{0}", 'a'));
EXPECT_EQ("z", format("{0:c}", 'z')); EXPECT_EQ("z", format("{0:c}", 'z'));