Add thousand separators on floating number with specifier 'n'

This commit is contained in:
Daniel Laügt 2019-11-10 22:26:05 +01:00
parent 1f918159ed
commit baa84eb810
4 changed files with 244 additions and 125 deletions

View File

@ -220,7 +220,7 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
#else #else
template <typename Char> template <typename Char>
FMT_FUNC std::string internal::grouping_impl(locale_ref) { FMT_FUNC std::string internal::grouping_impl(locale_ref) {
return "\03"; return "\3";
} }
template <typename Char> template <typename Char>
FMT_FUNC Char internal::thousands_sep_impl(locale_ref) { FMT_FUNC Char internal::thousands_sep_impl(locale_ref) {
@ -1143,7 +1143,7 @@ bool grisu_format(Double value, buffer<char>& buf, int precision,
template <typename Double> template <typename Double>
char* sprintf_format(Double value, internal::buffer<char>& buf, char* sprintf_format(Double value, internal::buffer<char>& buf,
sprintf_specs specs) { sprintf_specs specs, bool& fixed) {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() != 0, "empty buffer"); FMT_ASSERT(buf.capacity() != 0, "empty buffer");
@ -1171,6 +1171,7 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
*format_ptr = '\0'; *format_ptr = '\0';
// Format using snprintf. // Format using snprintf.
fixed = true;
char* start = nullptr; char* start = nullptr;
char* decimal_point_pos = nullptr; char* decimal_point_pos = nullptr;
for (;;) { for (;;) {
@ -1186,7 +1187,10 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
if (*p == '+' || *p == '-') ++p; if (*p == '+' || *p == '-') ++p;
if (specs.type != 'a' && specs.type != 'A') { if (specs.type != 'a' && specs.type != 'A') {
while (p < end && *p >= '0' && *p <= '9') ++p; while (p < end && *p >= '0' && *p <= '9') ++p;
if (p < end && *p != 'e' && *p != 'E') { if (p < end) {
if (*p == 'e' || *p == 'E') {
fixed = false;
} else {
decimal_point_pos = p; decimal_point_pos = p;
if (!specs.type) { if (!specs.type) {
// Keep only one trailing zero after the decimal point. // Keep only one trailing zero after the decimal point.
@ -1202,6 +1206,7 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
} }
} }
} }
}
buf.resize(n); buf.resize(n);
break; // The buffer is large enough - continue with formatting. break; // The buffer is large enough - continue with formatting.
} }

View File

@ -850,33 +850,27 @@ template <> inline wchar_t decimal_point(locale_ref loc) {
} }
// Formats a decimal unsigned integer value writing into buffer. // Formats a decimal unsigned integer value writing into buffer.
// add_thousands_sep is called after writing each char to add a thousands template <typename UInt>
// separator if necessary. inline char* format_decimal(char* buffer, UInt value, int num_digits) {
template <typename UInt, typename Char, typename F>
inline Char* format_decimal(Char* buffer, UInt value, int num_digits,
F add_thousands_sep) {
FMT_ASSERT(num_digits >= 0, "invalid digit count"); FMT_ASSERT(num_digits >= 0, "invalid digit count");
buffer += num_digits; buffer += num_digits;
Char* end = buffer; char* end = buffer;
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
// of for every digit. The idea comes from the talk by Alexandrescu // of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison. // "Three Optimization Tips for C++". See speed-test for a comparison.
auto index = static_cast<unsigned>((value % 100) * 2); auto index = static_cast<unsigned>((value % 100) * 2);
value /= 100; value /= 100;
*--buffer = static_cast<Char>(data::digits[index + 1]); *--buffer = data::digits[index + 1];
add_thousands_sep(buffer); *--buffer = data::digits[index];
*--buffer = static_cast<Char>(data::digits[index]);
add_thousands_sep(buffer);
} }
if (value < 10) { if (value < 10) {
*--buffer = static_cast<Char>('0' + value); *--buffer = static_cast<char>('0' + value);
return end; return end;
} }
auto index = static_cast<unsigned>(value * 2); auto index = static_cast<unsigned>(value * 2);
*--buffer = static_cast<Char>(data::digits[index + 1]); *--buffer = data::digits[index + 1];
add_thousands_sep(buffer); *--buffer = data::digits[index];
*--buffer = static_cast<Char>(data::digits[index]);
return end; return end;
} }
@ -886,20 +880,93 @@ template <typename Int> constexpr int digits10() noexcept {
template <> constexpr int digits10<int128_t>() noexcept { return 38; } template <> constexpr int digits10<int128_t>() noexcept { return 38; }
template <> constexpr int digits10<uint128_t>() noexcept { return 38; } template <> constexpr int digits10<uint128_t>() noexcept { return 38; }
template <typename Char, typename UInt, typename Iterator, typename F> template <typename Char> class thousand_formatter {
inline Iterator format_decimal(Iterator out, UInt value, int num_digits, const std::string& groups_;
F add_thousands_sep) { Char sep_;
int num_digits_;
std::string::const_reverse_iterator group_;
unsigned count_;
int pos_;
size_t size_;
public:
thousand_formatter(const std::string& groups, Char sep, int num_digits)
: groups_(groups),
sep_(sep),
num_digits_(num_digits),
group_(groups.crbegin()),
count_(0),
pos_(0),
size_(0) {
auto group = groups_.cbegin();
while (group != groups_.cend() && num_digits > *group && *group > 0 &&
*group != max_value<char>()) {
pos_ += *group;
++size_;
num_digits -= *group;
++group;
}
if (group == groups.cend()) {
count_ = (num_digits - 1) / groups.back();
pos_ += count_ * groups.back();
size_ += count_;
++count_;
} else {
size_t dist = group - groups_.cbegin();
group_ += groups.end() - group - (dist == size_ ? 1 : 0);
}
}
size_t size() const { return size_; }
template <typename It> void operator()(char c, It&& it) {
*it++ = static_cast<Char>(c);
--num_digits_;
if (num_digits_ != pos_ || !num_digits_) return;
*it++ = sep_;
if (count_)
--count_;
else
++group_;
pos_ -= *group_;
}
template <typename It>
void operator()(const char* begin, const char* end, It&& it) {
while (begin != end) {
operator()(*begin, it);
++begin;
}
}
};
template <typename Char> class no_thousand_formatter {
public:
size_t size() const { return 0; }
template <typename It> void operator()(char c, It&& it) {
*it++ = static_cast<Char>(c);
}
template <typename It>
void operator()(const char* begin, const char* end, It&& it) {
it = copy_str<Char>(begin, end, it);
}
};
template <typename Char, typename UInt, typename Iterator,
typename F = no_thousand_formatter<Char>>
inline Iterator format_decimal(
Iterator out, UInt value, int num_digits,
F add_thousands_sep = no_thousand_formatter<Char>{}) {
FMT_ASSERT(num_digits >= 0, "invalid digit count"); FMT_ASSERT(num_digits >= 0, "invalid digit count");
// Buffer should be large enough to hold all digits (<= digits10 + 1). // Buffer should be large enough to hold all digits (<= digits10 + 1).
enum { max_size = digits10<UInt>() + 1 }; enum { max_size = digits10<UInt>() + 1 };
Char buffer[2 * max_size]; char buffer[max_size];
auto end = format_decimal(buffer, value, num_digits, add_thousands_sep); auto end = format_decimal(buffer, value, num_digits);
return internal::copy_str<Char>(buffer, end, out); add_thousands_sep(buffer, end, out);
} return out;
template <typename Char, typename It, typename UInt>
inline It format_decimal(It out, UInt value, int num_digits) {
return format_decimal<Char>(out, value, num_digits, [](Char*) {});
} }
template <unsigned BASE_BITS, typename Char, typename UInt> template <unsigned BASE_BITS, typename Char, typename UInt>
@ -1078,7 +1145,8 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
return it; return it;
} }
template <typename Char> class grisu_writer { template <typename Char, typename F = no_thousand_formatter<Char>>
class grisu_writer {
private: private:
// The number is given as v = digits_ * pow(10, exp_). // The number is given as v = digits_ * pow(10, exp_).
const char* digits_; const char* digits_;
@ -1087,6 +1155,7 @@ template <typename Char> class grisu_writer {
size_t size_; size_t size_;
gen_digits_params params_; gen_digits_params params_;
Char decimal_point_; Char decimal_point_;
F add_thousands_sep_;
template <typename It> It grisu_prettify(It it) const { template <typename It> It grisu_prettify(It it) const {
// pow(10, full_exp - 1) <= v <= pow(10, full_exp). // pow(10, full_exp - 1) <= v <= pow(10, full_exp).
@ -1101,8 +1170,13 @@ template <typename Char> class grisu_writer {
} }
if (num_digits_ <= full_exp) { if (num_digits_ <= full_exp) {
// 1234e7 -> 12340000000[.0+] // 1234e7 -> 12340000000[.0+]
it = copy_str<Char>(digits_, digits_ + num_digits_, it); typename std::decay<F>::type add_thousands_sep(add_thousands_sep_);
it = std::fill_n(it, full_exp - num_digits_, static_cast<Char>('0')); add_thousands_sep(digits_, digits_ + num_digits_, it);
int n = full_exp - num_digits_;
while (n > 0) {
add_thousands_sep('0', it);
--n;
}
int num_zeros = (std::max)(params_.num_digits - full_exp, 1); int num_zeros = (std::max)(params_.num_digits - full_exp, 1);
if (params_.trailing_zeros) { if (params_.trailing_zeros) {
*it++ = decimal_point_; *it++ = decimal_point_;
@ -1114,7 +1188,8 @@ template <typename Char> class grisu_writer {
} }
} else if (full_exp > 0) { } else if (full_exp > 0) {
// 1234e-2 -> 12.34[0+] // 1234e-2 -> 12.34[0+]
it = copy_str<Char>(digits_, digits_ + full_exp, it); typename std::decay<F>::type add_thousands_sep(add_thousands_sep_);
add_thousands_sep(digits_, digits_ + full_exp, it);
if (!params_.trailing_zeros) { if (!params_.trailing_zeros) {
// Remove trailing zeros. // Remove trailing zeros.
int num_digits = num_digits_; int num_digits = num_digits_;
@ -1150,12 +1225,14 @@ template <typename Char> class grisu_writer {
public: public:
grisu_writer(const char* digits, int num_digits, int exp, grisu_writer(const char* digits, int num_digits, int exp,
gen_digits_params params, Char decimal_point) gen_digits_params params, Char decimal_point,
F add_thousands_sep = no_thousand_formatter<Char>{})
: digits_(digits), : digits_(digits),
num_digits_(num_digits), num_digits_(num_digits),
exp_(exp), exp_(exp),
params_(params), params_(params),
decimal_point_(decimal_point) { decimal_point_(decimal_point),
add_thousands_sep_(add_thousands_sep) {
int full_exp = num_digits + exp - 1; int full_exp = num_digits + exp - 1;
int precision = params.num_digits > 0 ? params.num_digits : 16; int precision = params.num_digits > 0 ? params.num_digits : 16;
params_.fixed |= full_exp >= -4 && full_exp < precision; params_.fixed |= full_exp >= -4 && full_exp < precision;
@ -1198,13 +1275,13 @@ struct sprintf_specs {
}; };
template <typename Double> template <typename Double>
char* sprintf_format(Double, internal::buffer<char>&, sprintf_specs); char* sprintf_format(Double, internal::buffer<char>&, sprintf_specs, bool&);
template <> template <>
inline char* sprintf_format<float>(float value, internal::buffer<char>& buf, inline char* sprintf_format<float>(float value, internal::buffer<char>& buf,
sprintf_specs specs) { sprintf_specs specs, bool& fixed) {
// printf does not have a float format specifier, it only supports double. // printf does not have a float format specifier, it only supports double.
return sprintf_format<double>(value, buf, specs); return sprintf_format<double>(value, buf, specs, fixed);
} }
template <typename Handler> template <typename Handler>
@ -1398,7 +1475,7 @@ template <typename Range> class basic_writer {
size_t size() const { return size_; } size_t size() const { return size_; }
size_t width() const { return size_; } size_t width() const { return size_; }
template <typename It> void operator()(It&& it) const { template <typename It> void operator()(It&& it) {
if (prefix.size() != 0) if (prefix.size() != 0)
it = internal::copy_str<char_type>(prefix.begin(), prefix.end(), it); it = internal::copy_str<char_type>(prefix.begin(), prefix.end(), it);
it = std::fill_n(it, padding, fill); it = std::fill_n(it, padding, fill);
@ -1469,19 +1546,28 @@ template <typename Range> class basic_writer {
} }
} }
struct dec_writer { template <typename F = no_thousand_formatter<char_type>> class dec_writer {
unsigned_type abs_value; unsigned_type abs_value_;
int num_digits; int num_digits_;
F add_thousands_sep_;
template <typename It> void operator()(It&& it) const { public:
it = internal::format_decimal<char_type>(it, abs_value, num_digits); dec_writer(unsigned_type abs_value, int num_digits,
F add_thousands_sep = no_thousand_formatter<char_type>{})
: abs_value_(abs_value),
num_digits_(num_digits),
add_thousands_sep_(add_thousands_sep) {}
template <typename It> void operator()(It&& it) {
it = internal::format_decimal<char_type>(it, abs_value_, num_digits_,
add_thousands_sep_);
} }
}; };
void on_dec() { void on_dec() {
int num_digits = internal::count_digits(abs_value); int num_digits = internal::count_digits(abs_value);
writer.write_int(num_digits, get_prefix(), specs, writer.write_int(num_digits, get_prefix(), specs,
dec_writer{abs_value, num_digits}); dec_writer<>(abs_value, num_digits));
} }
struct hex_writer { struct hex_writer {
@ -1534,55 +1620,18 @@ template <typename Range> class basic_writer {
bin_writer<3>{abs_value, num_digits}); bin_writer<3>{abs_value, num_digits});
} }
enum { sep_size = 1 };
struct num_writer {
unsigned_type abs_value;
int size;
const std::string& groups;
char_type sep;
template <typename It> void operator()(It&& it) const {
basic_string_view<char_type> s(&sep, sep_size);
// Index of a decimal digit with the least significant digit having
// index 0.
unsigned digit_index = 0;
std::string::const_iterator group = groups.cbegin();
it = internal::format_decimal<char_type>(
it, abs_value, size,
[this, s, &group, &digit_index](char_type*& buffer) {
if (*group <= 0 || ++digit_index % *group != 0 ||
*group == max_value<char>())
return;
if (group + 1 != groups.cend()) {
digit_index = 0;
++group;
}
buffer -= s.size();
std::uninitialized_copy(s.data(), s.data() + s.size(),
internal::make_checked(buffer, s.size()));
});
}
};
void on_num() { void on_num() {
std::string groups = internal::grouping<char_type>(writer.locale_); std::string groups = internal::grouping<char_type>(writer.locale_);
if (groups.empty()) return on_dec(); if (groups.empty()) return on_dec();
auto sep = internal::thousands_sep<char_type>(writer.locale_); auto sep = internal::thousands_sep<char_type>(writer.locale_);
if (!sep) return on_dec(); if (!sep) return on_dec();
int num_digits = internal::count_digits(abs_value); int num_digits = internal::count_digits(abs_value);
int size = num_digits; thousand_formatter<char_type> add_thousands_sep{groups, sep, num_digits};
std::string::const_iterator group = groups.cbegin(); if (!add_thousands_sep.size()) return on_dec();
while (group != groups.cend() && num_digits > *group && *group > 0 && writer.write_int(num_digits + static_cast<int>(add_thousands_sep.size()),
*group != max_value<char>()) { get_prefix(), specs,
size += sep_size; dec_writer<const thousand_formatter<char_type>&>(
num_digits -= *group; abs_value, num_digits, add_thousands_sep));
++group;
}
if (group == groups.cend())
size += sep_size * ((num_digits - 1) / groups.back());
writer.write_int(size, get_prefix(), specs,
num_writer{abs_value, size, groups, sep});
} }
FMT_NORETURN void on_error() { FMT_NORETURN void on_error() {
@ -1611,24 +1660,38 @@ template <typename Range> class basic_writer {
} }
}; };
struct double_writer { template <typename F = no_thousand_formatter<char_type>> class double_writer {
sign_t sign; sign_t sign_;
internal::buffer<char>& buffer; internal::buffer<char>& buffer_;
char* decimal_point_pos; char* decimal_point_pos_;
char_type decimal_point; char_type decimal_point_;
F add_thousands_sep_;
size_t size() const { return buffer.size() + (sign ? 1 : 0); } public:
double_writer(sign_t sign, internal::buffer<char>& buffer,
char* decimal_point_pos, char_type decimal_point,
F add_thousands_sep = no_thousand_formatter<char_type>{})
: sign_(sign),
buffer_(buffer),
decimal_point_pos_(decimal_point_pos),
decimal_point_(decimal_point),
add_thousands_sep_(add_thousands_sep) {}
size_t size() const {
return buffer_.size() + add_thousands_sep_.size() + (sign_ ? 1 : 0);
}
size_t width() const { return size(); } size_t width() const { return size(); }
template <typename It> void operator()(It&& it) { template <typename It> void operator()(It&& it) {
if (sign) *it++ = static_cast<char_type>(data::signs[sign]); if (sign_) *it++ = static_cast<char_type>(data::signs[sign_]);
auto begin = buffer.begin(); if (decimal_point_pos_) {
if (decimal_point_pos) { add_thousands_sep_(buffer_.begin(), decimal_point_pos_, it);
it = internal::copy_str<char_type>(begin, decimal_point_pos, it); *it++ = decimal_point_;
*it++ = decimal_point; it = internal::copy_str<char_type>(decimal_point_pos_ + 1,
begin = decimal_point_pos + 1; buffer_.end(), it);
} else {
add_thousands_sep_(buffer_.begin(), buffer_.end(), it);
} }
it = internal::copy_str<char_type>(begin, buffer.end(), it);
} }
}; };
@ -2857,8 +2920,10 @@ void internal::basic_writer<Range>::write_fp(T value,
(specs.type != 'a' && specs.type != 'A' && specs.type != 'e' && (specs.type != 'a' && specs.type != 'A' && specs.type != 'e' &&
specs.type != 'E') && specs.type != 'E') &&
grisu_format(static_cast<double>(value), buffer, precision, options, exp); grisu_format(static_cast<double>(value), buffer, precision, options, exp);
bool fixed = false;
char* decimal_point_pos = nullptr; char* decimal_point_pos = nullptr;
if (!use_grisu) decimal_point_pos = sprintf_format(value, buffer, specs); if (!use_grisu)
decimal_point_pos = sprintf_format(value, buffer, specs, fixed);
if (handler.as_percentage) { if (handler.as_percentage) {
buffer.push_back('%'); buffer.push_back('%');
@ -2887,12 +2952,58 @@ void internal::basic_writer<Range>::write_fp(T value,
params.trailing_zeros = params.trailing_zeros =
(precision != 0 && (handler.fixed || !specs.type)) || specs.alt; (precision != 0 && (handler.fixed || !specs.type)) || specs.alt;
int num_digits = static_cast<int>(buffer.size()); int num_digits = static_cast<int>(buffer.size());
write_padded(as, grisu_writer<char_type>(buffer.data(), num_digits, exp,
params, decimal_point)); if (!handler.use_locale) {
} else { return write_padded(
write_padded(as, as, grisu_writer<char_type>(buffer.data(), num_digits, exp, params,
double_writer{sign, buffer, decimal_point_pos, decimal_point}); decimal_point));
} }
std::string groups = internal::grouping<char_type>(locale_);
if (groups.empty()) {
return write_padded(
as, grisu_writer<char_type>(buffer.data(), num_digits, exp, params,
decimal_point));
}
char_type sep = internal::thousands_sep<char_type>(locale_);
if (!sep) {
return write_padded(
as, grisu_writer<char_type>(buffer.data(), num_digits, exp, params,
decimal_point));
}
thousand_formatter<char_type> add(groups, sep, num_digits + exp);
if (!add.size()) {
return write_padded(
as, grisu_writer<char_type>(buffer.data(), num_digits, exp, params,
decimal_point));
}
return write_padded(
as, grisu_writer<char_type, const thousand_formatter<char_type>&>(
buffer.data(), num_digits, exp, params, decimal_point, add));
}
if (!handler.use_locale || !fixed) {
return write_padded(
as, double_writer<>(sign, buffer, decimal_point_pos, decimal_point));
}
std::string groups = internal::grouping<char_type>(locale_);
if (groups.empty()) {
return write_padded(
as, double_writer<>(sign, buffer, decimal_point_pos, decimal_point));
}
char_type sep = internal::thousands_sep<char_type>(locale_);
if (!sep) {
return write_padded(
as, double_writer<>(sign, buffer, decimal_point_pos, decimal_point));
}
int num_digits = static_cast<int>(
decimal_point_pos ? decimal_point_pos - buffer.begin() : buffer.size());
thousand_formatter<char_type> add(groups, sep, num_digits);
if (!add.size()) {
return write_padded(
as, double_writer<>(sign, buffer, decimal_point_pos, decimal_point));
}
write_padded(as, double_writer<thousand_formatter<char_type>&>(
sign, buffer, decimal_point_pos, decimal_point, add));
} }
// Reports a system error without throwing an exception. // Reports a system error without throwing an exception.

View File

@ -37,10 +37,10 @@ template FMT_API format_context::iterator internal::vformat_to(
internal::buffer<char>&, string_view, basic_format_args<format_context>); internal::buffer<char>&, string_view, basic_format_args<format_context>);
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&, template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
sprintf_specs); sprintf_specs, bool& fixed);
template FMT_API char* internal::sprintf_format(long double, template FMT_API char* internal::sprintf_format(long double,
internal::buffer<char>&, internal::buffer<char>&,
sprintf_specs); sprintf_specs, bool& fixed);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.

View File

@ -14,7 +14,7 @@ using fmt::internal::max_value;
template <typename Char> struct numpunct : std::numpunct<Char> { template <typename Char> struct numpunct : std::numpunct<Char> {
protected: protected:
Char do_decimal_point() const FMT_OVERRIDE { return '?'; } Char do_decimal_point() const FMT_OVERRIDE { return '?'; }
std::string do_grouping() const FMT_OVERRIDE { return "\03"; } std::string do_grouping() const FMT_OVERRIDE { return "\3"; }
Char do_thousands_sep() const FMT_OVERRIDE { return '~'; } Char do_thousands_sep() const FMT_OVERRIDE { return '~'; }
}; };
@ -28,27 +28,30 @@ template <typename Char> struct no_grouping : std::numpunct<Char> {
template <typename Char> struct special_grouping : std::numpunct<Char> { template <typename Char> struct special_grouping : std::numpunct<Char> {
protected: protected:
Char do_decimal_point() const FMT_OVERRIDE { return '.'; } Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
std::string do_grouping() const FMT_OVERRIDE { return "\03\02"; } std::string do_grouping() const FMT_OVERRIDE { return "\3\2"; }
Char do_thousands_sep() const FMT_OVERRIDE { return ','; } Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
}; };
template <typename Char> struct small_grouping : std::numpunct<Char> { template <typename Char> struct small_grouping : std::numpunct<Char> {
protected: protected:
Char do_decimal_point() const FMT_OVERRIDE { return '.'; } Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
std::string do_grouping() const FMT_OVERRIDE { return "\01"; } std::string do_grouping() const FMT_OVERRIDE { return "\1"; }
Char do_thousands_sep() const FMT_OVERRIDE { return ','; } Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
}; };
TEST(LocaleTest, DoubleDecimalPoint) { TEST(LocaleTest, DoubleDecimalPoint) {
std::locale loc(std::locale(), new numpunct<char>()); std::locale loc(std::locale(), new numpunct<char>());
EXPECT_EQ("1?23", fmt::format(loc, "{:n}", 1.23)); EXPECT_EQ("1~234?56", fmt::format(loc, "{:n}", 1234.56));
EXPECT_EQ("1~230", fmt::format(loc, "{:n}", 123e1));
EXPECT_EQ("1?23456e+10", fmt::format(loc, "{:n}", 1.23456e10));
EXPECT_EQ("0?123456", fmt::format(loc, "{:n}", 123456e-6));
// Test with Grisu disabled. // Test with Grisu disabled.
fmt::memory_buffer buf; fmt::memory_buffer buf;
fmt::internal::writer w(buf, fmt::internal::locale_ref(loc)); fmt::internal::writer w(buf, fmt::internal::locale_ref(loc));
auto specs = fmt::format_specs(); auto specs = fmt::format_specs();
specs.type = 'n'; specs.type = 'n';
w.write_fp<double, false>(1.23, specs); w.write_fp<double, false>(1234.56, specs);
EXPECT_EQ(fmt::to_string(buf), "1?23"); EXPECT_EQ(fmt::to_string(buf), "1~234?56");
} }
TEST(LocaleTest, Format) { TEST(LocaleTest, Format) {