Skip to content

Commit

Permalink
Implement locale-specific integer formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Apr 18, 2016
1 parent 581afee commit f68771a
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 7 deletions.
54 changes: 49 additions & 5 deletions cppformat/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#define FMT_FORMAT_H_

#include <cassert>
#include <clocale>
#include <cmath>
#include <cstdio>
#include <cstring>
Expand Down Expand Up @@ -871,9 +872,38 @@ inline unsigned count_digits(uint32_t n) {
}
#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.
template <typename UInt, typename Char>
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 <typename UInt, typename Char, typename ThousandsSep>
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
Expand All @@ -882,7 +912,9 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {
unsigned index = static_cast<unsigned>((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<char>('0' + value);
Expand All @@ -893,6 +925,11 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {
*--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
# define FMT_USE_WINDOWS_H 0
#elif !defined(FMT_USE_WINDOWS_H)
Expand Down Expand Up @@ -2627,9 +2664,8 @@ void BasicWriter<Char>::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': {
Expand Down Expand Up @@ -2684,6 +2720,14 @@ void BasicWriter<Char>::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;

This comment has been minimized.

Copy link
@kiyoto-suzuki

kiyoto-suzuki Apr 26, 2016

the following warning is generated in Xcode

format.h:2727:36: Implicit conversion loses integer precision: 'std::size_t' (aka 'unsigned long') to 'unsigned int'

This comment has been minimized.

Copy link
@vitaut

vitaut Apr 26, 2016

Author Contributor

Should be fixed in 90accff. Thanks for catching this!

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");
Expand Down
15 changes: 13 additions & 2 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <cctype>
#include <cfloat>
#include <climits>
#include <clocale>
#include <cmath>
#include <cstring>
#include <fstream>
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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'));
Expand Down

1 comment on commit f68771a

@rogerdahl
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! :)

Please sign in to comment.