mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-29 02:17:28 +08:00
Add separator choice to the ' digit grouping option
Extend the grammar to grouping ::= "'" [" " | "," | "_" | "'"]:
- ' : U+202F (U+0020 for char if the literal encoding is not UTF-8),
groups the integer and fraction parts, may be combined with L
- ' (apostrophe and space): U+0020, otherwise the same as '
- ', '_ '' : the given character, groups only the integer part;
combining them with L is an error
All forms share the presentation type restrictions, the grouping of
zero padding and the four-digit exception macro. The separator kind is
stored in a 3-bit field of basic_specs (fmt::group_separator).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7ba4db88b1
commit
f8abbeffc0
@ -63,10 +63,11 @@ fmt's parser.
|
||||
|
||||
<a id="format-spec"></a>
|
||||
<pre><code class="language-json"
|
||||
>format_spec ::= [[fill]align][sign]["#"]["0"][width]["'"]["." precision]["L"][type]
|
||||
>format_spec ::= [[fill]align][sign]["#"]["0"][width][grouping]["." precision]["L"][type]
|
||||
fill ::= <a character other than '{' or '}'>
|
||||
align ::= "<" | ">" | "^"
|
||||
sign ::= "+" | "-" | " "
|
||||
grouping ::= "'" [" " | "," | "_" | "'"]
|
||||
width ::= <a href="#replacement-field">integer</a> | "{" [<a
|
||||
href="#replacement-field">arg_id</a>] "}"
|
||||
precision ::= <a href="#replacement-field">integer</a> | "{" [<a
|
||||
@ -223,33 +224,44 @@ fmt::format(loc, "{:.2Lf}", 1234567.89); // Result: "1,234,567.89"
|
||||
|
||||
### Digit grouping (`'`)
|
||||
|
||||
The `'` option groups the digits of integers and of floating-point values in
|
||||
decimal notation in threes, counting from the decimal marker towards the left
|
||||
and towards the right, as specified by ISO 80000-1 and the SI Brochure. The
|
||||
separator is U+202F NARROW NO-BREAK SPACE: a single code unit for wide
|
||||
character types and its UTF-8 encoding for `char` when the literal encoding is
|
||||
UTF-8, otherwise U+0020 SPACE. Each separator counts as one column of width.
|
||||
The *grouping* option groups the digits of integers and of floating-point
|
||||
values in decimal notation in threes, counting from the decimal marker. The
|
||||
character after `'` selects the separator:
|
||||
|
||||
| Option | Separator | Groups | With `L` |
|
||||
|--------|-----------|----------------------------|----------|
|
||||
| `'` | U+202F NARROW NO-BREAK SPACE | integer and fraction part | allowed |
|
||||
| `' ` | U+0020 SPACE | integer and fraction part | allowed |
|
||||
| `',` | `,` | integer part only | error |
|
||||
| `'_` | `_` | integer part only | error |
|
||||
| `''` | `'` | integer part only | error |
|
||||
|
||||
`'` alone follows ISO 80000-1 and the SI Brochure. U+202F is written as a
|
||||
single code unit for wide character types and as UTF-8 for `char` when the
|
||||
literal encoding is UTF-8; otherwise U+0020 SPACE is used. Each separator
|
||||
counts as one column of width. With `'` and `' `, `L` supplies only the
|
||||
decimal point; grouping never comes from the locale.
|
||||
|
||||
In exponential notation only the fraction digits of the significand are
|
||||
grouped, never the exponent. Infinity and NaN are not affected. With `0`, the
|
||||
padding zeros are grouped like other digits; the result never starts with a
|
||||
separator, so it can be one column wider than *width*. With `L`, the locale
|
||||
supplies only the decimal point; grouping always follows `'`.
|
||||
grouped (by `'` and `' `), never the exponent. Infinity and NaN are not
|
||||
affected. With `0`, the padding zeros are grouped like other digits; the
|
||||
result never starts with a separator, so it can be one column wider than
|
||||
*width*.
|
||||
|
||||
The option is accepted for integers (other than `bool` and characters) with
|
||||
presentation type `d` or none, and for floating-point values with presentation
|
||||
types `e`, `E`, `f`, `F`, `g`, `G` or none.
|
||||
|
||||
```c++
|
||||
fmt::format("{:'}", 1234567); // Result: "1 234 567"
|
||||
fmt::format("{:'.6f}", 1234567.891234); // Result: "1 234 567.891 234"
|
||||
fmt::format("{:'.6e}", 123456.789); // Result: "1.234 568e+05"
|
||||
fmt::format("{:*>12'}", 1234567); // Result: "***1 234 567"
|
||||
fmt::format("{:010'}", 1234567); // Result: "01 234 567"
|
||||
fmt::format("{:'}", 1234567); // Result: "1 234 567" (U+202F)
|
||||
fmt::format("{:'.6f}", 1234567.891234); // Result: "1 234 567.891 234" (U+202F)
|
||||
fmt::format("{:' .6e}", 123456.789); // Result: "1.234 568e+05"
|
||||
fmt::format("{:',.6f}", 1234567.891234); // Result: "1,234,567.891234"
|
||||
fmt::format("{:'_}", 1234567); // Result: "1_234_567"
|
||||
fmt::format("{:*>12''}", 1234567); // Result: "***1'234'567"
|
||||
fmt::format("{:010',}", 1234567); // Result: "01,234,567"
|
||||
```
|
||||
|
||||
(The spaces in the results above are U+202F.)
|
||||
|
||||
### Presentation type
|
||||
|
||||
The *type* field chooses the representation for the value. Specifiers are
|
||||
|
||||
@ -677,6 +677,12 @@ enum class presentation_type : unsigned char {
|
||||
|
||||
enum class align { none, left, right, center, numeric };
|
||||
enum class sign { none, minus, plus, space };
|
||||
// Digit group separator selected by the ' option. iso (') is U+202F, or
|
||||
// U+0020 for char if the literal encoding is not UTF-8; space (' followed by
|
||||
// a space) is U+0020; comma (',), underscore ('_) and apostrophe ('') are the
|
||||
// corresponding characters. iso and space group both the integer and the
|
||||
// fraction part, the others group only the integer part.
|
||||
enum class group_separator { none, iso, space, comma, underscore, apostrophe };
|
||||
enum class arg_id_kind { none, index, name };
|
||||
|
||||
// Basic format specifiers for built-in and string types.
|
||||
@ -687,8 +693,8 @@ class basic_specs {
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |type |align| w | p | s |u|#|L| f |'| unused |
|
||||
// +-----+-----+---+---+---+-+-+-+-----+-+-------------------------+
|
||||
// |type |align| w | p | s |u|#|L| f | ' | unused |
|
||||
// +-----+-----+---+---+---+-+-+-+-----+-----+---------------------+
|
||||
//
|
||||
// w - dynamic width info
|
||||
// p - dynamic precision info
|
||||
@ -697,7 +703,7 @@ class basic_specs {
|
||||
// # - alternate form ('#')
|
||||
// L - localized
|
||||
// f - fill size
|
||||
// ' - ISO 80000-1 digit grouping
|
||||
// ' - digit grouping (fmt::group_separator)
|
||||
//
|
||||
// Bitfields are not used because of compiler bugs such as gcc bug 61414.
|
||||
enum : unsigned {
|
||||
@ -710,19 +716,20 @@ class basic_specs {
|
||||
alternate_mask = 0x02000,
|
||||
localized_mask = 0x04000,
|
||||
fill_size_mask = 0x38000,
|
||||
iso_grouping_mask = 0x40000,
|
||||
grouping_mask = 0x1C0000,
|
||||
|
||||
align_shift = 3,
|
||||
width_shift = 6,
|
||||
precision_shift = 8,
|
||||
sign_shift = 10,
|
||||
fill_size_shift = 15,
|
||||
grouping_shift = 18,
|
||||
|
||||
max_fill_size = 4
|
||||
};
|
||||
|
||||
unsigned data_ = 1 << fill_size_shift;
|
||||
static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 19, "");
|
||||
static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 21, "");
|
||||
|
||||
// Character (code unit) type is erased to prevent template bloat.
|
||||
char fill_data_[max_fill_size] = {' '};
|
||||
@ -785,13 +792,17 @@ class basic_specs {
|
||||
FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; }
|
||||
FMT_CONSTEXPR void clear_localized() { data_ &= ~localized_mask; }
|
||||
|
||||
// Digit grouping in threes on both sides of the decimal marker as specified
|
||||
// by ISO 80000-1 and the SI Brochure (the ' option).
|
||||
constexpr auto iso_grouping() const -> bool {
|
||||
return (data_ & iso_grouping_mask) != 0;
|
||||
// Digit grouping in threes counted from the decimal marker (the ' option).
|
||||
constexpr auto grouping() const -> fmt::group_separator {
|
||||
return static_cast<fmt::group_separator>((data_ & grouping_mask) >>
|
||||
grouping_shift);
|
||||
}
|
||||
FMT_CONSTEXPR void set_grouping(fmt::group_separator g) {
|
||||
data_ = (data_ & ~grouping_mask) | (unsigned(g) << grouping_shift);
|
||||
}
|
||||
constexpr auto iso_grouping() const -> bool {
|
||||
return (data_ & grouping_mask) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR void set_iso_grouping() { data_ |= iso_grouping_mask; }
|
||||
FMT_CONSTEXPR void clear_iso_grouping() { data_ &= ~iso_grouping_mask; }
|
||||
|
||||
constexpr auto fill_size() const -> size_t {
|
||||
return (data_ & fill_size_mask) >> fill_size_shift;
|
||||
@ -1561,8 +1572,27 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end,
|
||||
case '\'':
|
||||
enter_state(state::iso_grouping,
|
||||
in(arg_type, sint_set | uint_set | float_set));
|
||||
specs.set_iso_grouping();
|
||||
++begin;
|
||||
specs.set_grouping(group_separator::iso);
|
||||
if (++begin != end) {
|
||||
switch (to_ascii(*begin)) {
|
||||
case ' ':
|
||||
specs.set_grouping(group_separator::space);
|
||||
++begin;
|
||||
break;
|
||||
case ',':
|
||||
specs.set_grouping(group_separator::comma);
|
||||
++begin;
|
||||
break;
|
||||
case '_':
|
||||
specs.set_grouping(group_separator::underscore);
|
||||
++begin;
|
||||
break;
|
||||
case '\'':
|
||||
specs.set_grouping(group_separator::apostrophe);
|
||||
++begin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '.':
|
||||
enter_state(state::precision,
|
||||
@ -1571,6 +1601,9 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end,
|
||||
break;
|
||||
case 'L':
|
||||
enter_state(state::locale, is_arithmetic_type(arg_type));
|
||||
// L may only supply the decimal point for the ISO forms (' and ' ).
|
||||
if (specs.grouping() > group_separator::space)
|
||||
report_error("invalid digit group separator with 'L'");
|
||||
specs.set_localized();
|
||||
++begin;
|
||||
break;
|
||||
|
||||
@ -2417,18 +2417,36 @@ template <typename Char> struct iso_separator {
|
||||
size_t size; // In code units; the display width is always 1.
|
||||
};
|
||||
|
||||
// Returns the digit group separator: U+202F NARROW NO-BREAK SPACE encoded as
|
||||
// a single code unit for wide character types, as UTF-8 for char if the
|
||||
// literal encoding is UTF-8, and U+0020 SPACE otherwise.
|
||||
// Returns the digit group separator for g: U+202F NARROW NO-BREAK SPACE for
|
||||
// group_separator::iso, encoded as a single code unit for wide character types,
|
||||
// as UTF-8 for char if the literal encoding is UTF-8, and U+0020 SPACE
|
||||
// otherwise. The other forms use the corresponding ASCII character.
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto iso_digit_separator() -> iso_separator<Char> {
|
||||
FMT_CONSTEXPR auto iso_digit_separator(group_separator g)
|
||||
-> iso_separator<Char> {
|
||||
switch (g) {
|
||||
case group_separator::space: return {{Char(' '), Char(), Char()}, 1};
|
||||
case group_separator::comma: return {{Char(','), Char(), Char()}, 1};
|
||||
case group_separator::underscore: return {{Char('_'), Char(), Char()}, 1};
|
||||
case group_separator::apostrophe: return {{Char('\''), Char(), Char()}, 1};
|
||||
default: break;
|
||||
}
|
||||
return {{static_cast<Char>(0x202F), Char(), Char()}, 1};
|
||||
}
|
||||
template <>
|
||||
FMT_CONSTEXPR inline auto iso_digit_separator<char>() -> iso_separator<char> {
|
||||
if (FMT_UNICODE != 0 && is_utf8_enabled != 0)
|
||||
return {{'\xE2', '\x80', '\xAF'}, 3};
|
||||
return {{' ', '\0', '\0'}, 1};
|
||||
FMT_CONSTEXPR inline auto iso_digit_separator<char>(group_separator g)
|
||||
-> iso_separator<char> {
|
||||
if (g == group_separator::iso) {
|
||||
if (FMT_UNICODE != 0 && is_utf8_enabled != 0)
|
||||
return {{'\xE2', '\x80', '\xAF'}, 3};
|
||||
g = group_separator::space;
|
||||
}
|
||||
switch (g) {
|
||||
case group_separator::comma: return {{',', '\0', '\0'}, 1};
|
||||
case group_separator::underscore: return {{'_', '\0', '\0'}, 1};
|
||||
case group_separator::apostrophe: return {{'\'', '\0', '\0'}, 1};
|
||||
default: return {{' ', '\0', '\0'}, 1};
|
||||
}
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline auto is_iso_digit(char c) -> bool {
|
||||
@ -2469,7 +2487,9 @@ FMT_CONSTEXPR auto write_iso_grouped(OutputIt out, const char* begin,
|
||||
}
|
||||
int digits_size = static_cast<int>(int_end - int_begin);
|
||||
int frac_size = static_cast<int>(frac_end - frac_begin);
|
||||
int frac_seps = iso_separator_count(frac_size);
|
||||
// Only the ISO forms (' and ' ) group the fraction digits.
|
||||
bool group_frac = specs.grouping() <= group_separator::space;
|
||||
int frac_seps = group_frac ? iso_separator_count(frac_size) : 0;
|
||||
auto num_chars = to_unsigned(end - begin);
|
||||
|
||||
auto padded_specs = specs;
|
||||
@ -2492,7 +2512,7 @@ FMT_CONSTEXPR auto write_iso_grouped(OutputIt out, const char* begin,
|
||||
}
|
||||
int num_zeros = int_size - digits_size;
|
||||
int int_seps = iso_separator_count(int_size);
|
||||
auto sep = iso_digit_separator<Char>();
|
||||
auto sep = iso_digit_separator<Char>(specs.grouping());
|
||||
auto num_seps = to_unsigned(int_seps + frac_seps);
|
||||
// Each separator is one code point of display width 1 but may take several
|
||||
// code units, so size and width are passed to write_padded separately.
|
||||
@ -3855,7 +3875,7 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs,
|
||||
// separators and pad. ' takes precedence over L for grouping; L only
|
||||
// supplies the decimal point.
|
||||
auto plain_specs = specs;
|
||||
plain_specs.clear_iso_grouping();
|
||||
plain_specs.set_grouping(group_separator::none);
|
||||
plain_specs.clear_localized();
|
||||
plain_specs.set_align(align::none);
|
||||
plain_specs.width = 0;
|
||||
|
||||
@ -256,7 +256,7 @@ if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
format-iso-grouping
|
||||
"
|
||||
#if FMT_USE_CONSTEVAL
|
||||
fmt::format(\"{:>12'.3f} {:'} {:010'} {:'L}\", 1.5, 42, 42, 42);
|
||||
fmt::format(\"{:>12'.3f} {:'} {:010'} {:'L} {:' L} {:',} {:'_.2f} {:''} {:'>8''}\", 1.5, 42, 42, 42, 42, 42, 1.5, 42, 42);
|
||||
#endif
|
||||
")
|
||||
expect_compile(
|
||||
@ -320,6 +320,37 @@ if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
"
|
||||
ERROR)
|
||||
|
||||
expect_compile(
|
||||
format-iso-grouping-comma-locale-error
|
||||
"
|
||||
#if FMT_USE_CONSTEVAL
|
||||
fmt::format(\"{:',L}\", 42);
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
"
|
||||
ERROR)
|
||||
expect_compile(
|
||||
format-iso-grouping-underscore-hex-error
|
||||
"
|
||||
#if FMT_USE_CONSTEVAL
|
||||
fmt::format(\"{:'_x}\", 42);
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
"
|
||||
ERROR)
|
||||
expect_compile(
|
||||
format-iso-grouping-apostrophe-locale-float-error
|
||||
"
|
||||
#if FMT_USE_CONSTEVAL
|
||||
fmt::format(\"{:''.2Lf}\", 4.2);
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
"
|
||||
ERROR)
|
||||
|
||||
# Compile-time argument name check
|
||||
expect_compile(
|
||||
format-string-name
|
||||
|
||||
@ -1350,12 +1350,114 @@ TEST(format_test, iso_digit_grouping) {
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'010}"), 42), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:''}"), 42), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2'f}"), 4.2), format_error,
|
||||
"invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, digit_group_separators) {
|
||||
// ' followed by space always uses U+0020 and groups both parts like '.
|
||||
EXPECT_EQ(fmt::format("{:' }", 1234567), "1 234 567");
|
||||
EXPECT_EQ(fmt::format("{:' .6f}", 1234567.891234), "1 234 567.891 234");
|
||||
EXPECT_EQ(fmt::format("{:' .6e}", 123456.789), "1.234 568e+05");
|
||||
EXPECT_EQ(fmt::format("{:' }", 0.000123456), "0.000 123 456");
|
||||
EXPECT_EQ(fmt::format("{:>12' }", -1234567), " -1 234 567");
|
||||
EXPECT_EQ(fmt::format("{:010' }", 1234567), "01 234 567");
|
||||
// In a UTF-8 build ' and ' differ only in the separator.
|
||||
if (fmt::detail::is_utf8_enabled != 0 && FMT_UNICODE != 0) {
|
||||
EXPECT_EQ(fmt::format("{:'}", 1234567),
|
||||
"1\xE2\x80\xAF"
|
||||
"234\xE2\x80\xAF"
|
||||
"567");
|
||||
EXPECT_NE(fmt::format("{:'}", 1234567), fmt::format("{:' }", 1234567));
|
||||
EXPECT_EQ(fmt::format("{:>12'}", 1234567).size(),
|
||||
fmt::format("{:>12' }", 1234567).size() + 2 * 2);
|
||||
}
|
||||
|
||||
// ', '_ and '' group only the integer part.
|
||||
EXPECT_EQ(fmt::format("{:',}", 1234567), "1,234,567");
|
||||
EXPECT_EQ(fmt::format("{:'_}", 1234567), "1_234_567");
|
||||
EXPECT_EQ(fmt::format("{:''}", 1234567), "1'234'567");
|
||||
EXPECT_EQ(fmt::format("{:',}", 0), "0");
|
||||
EXPECT_EQ(fmt::format("{:',}", 123), "123");
|
||||
EXPECT_EQ(fmt::format("{:',}", 1234), "1,234");
|
||||
EXPECT_EQ(fmt::format("{:',}", -1234567), "-1,234,567");
|
||||
EXPECT_EQ(fmt::format("{:+'_d}", 1234567), "+1_234_567");
|
||||
EXPECT_EQ(fmt::format("{:',}", INT64_MIN), "-9,223,372,036,854,775,808");
|
||||
EXPECT_EQ(fmt::format("{:''}", UINT64_MAX), "18'446'744'073'709'551'615");
|
||||
EXPECT_EQ(fmt::format("{:',.6f}", 1234567.891234), "1,234,567.891234");
|
||||
EXPECT_EQ(fmt::format("{:'_.6f}", 1234567.891234), "1_234_567.891234");
|
||||
EXPECT_EQ(fmt::format("{:''.6f}", 1234567.891234), "1'234'567.891234");
|
||||
EXPECT_EQ(fmt::format("{:',.4f}", 0.1234567), "0.1235");
|
||||
EXPECT_EQ(fmt::format("{:'_.3e}", 123456.789), "1.235e+05");
|
||||
EXPECT_EQ(fmt::format("{:',.6e}", 123456.789), "1.234568e+05");
|
||||
EXPECT_EQ(fmt::format("{:',}", 1234567.891234), "1,234,567.891234");
|
||||
EXPECT_EQ(fmt::format("{:',}", 1.234567e300), "1.234567e+300");
|
||||
EXPECT_EQ(fmt::format("{:'_g}", 1234.5), "1_234.5");
|
||||
EXPECT_EQ(fmt::format("{:',.10G}", 1234567.891), "1,234,567.891");
|
||||
EXPECT_EQ(fmt::format("{:#',.0f}", 1234.0), "1,234.");
|
||||
EXPECT_EQ(fmt::format("{:',.0f}", 1e20), "100,000,000,000,000,000,000");
|
||||
EXPECT_EQ(fmt::format("{:',}", std::numeric_limits<double>::infinity()),
|
||||
"inf");
|
||||
|
||||
// Width and fill.
|
||||
EXPECT_EQ(fmt::format("{:*>12',}", 1234567), "***1,234,567");
|
||||
EXPECT_EQ(fmt::format("{:<12'_}", 1234567), "1_234_567 ");
|
||||
EXPECT_EQ(fmt::format("{:^13''}", 1234567), " 1'234'567 ");
|
||||
// ' as fill, then '' as grouping.
|
||||
EXPECT_EQ(fmt::format("{:'>8''}", 1234), "'''1'234");
|
||||
EXPECT_EQ(fmt::format("{:'>12'}", 1234), iso("'''''''1_234"));
|
||||
|
||||
// Zero padding groups the zeros with the same rule as '.
|
||||
EXPECT_EQ(fmt::format("{:010',}", 1234567), "01,234,567");
|
||||
EXPECT_EQ(fmt::format("{:04',}", 1), "0,001");
|
||||
EXPECT_EQ(fmt::format("{:03',}", 1), "001");
|
||||
EXPECT_EQ(fmt::format("{:010',}", -1234), "-0,001,234");
|
||||
EXPECT_EQ(fmt::format("{:08'_}", 1), "0_000_001");
|
||||
EXPECT_EQ(fmt::format("{:012',.2f}", 1234.5), "0,001,234.50");
|
||||
EXPECT_EQ(fmt::format("{:014',.6f}", 1.234567), "000,001.234567");
|
||||
EXPECT_EQ(fmt::format("{:012',.6f}", 1.234567), "0,001.234567");
|
||||
EXPECT_EQ(fmt::format("{:012''.3e}", 123456.0), "0'001.235e+05");
|
||||
EXPECT_EQ(fmt::format(runtime("{:010',}"), 1234567), "01,234,567");
|
||||
|
||||
// Invalid presentation types for every form.
|
||||
for (auto spec :
|
||||
{"{:'x}", "{:' x}", "{:',x}", "{:'_x}", "{:''x}", "{:'X}", "{:',X}",
|
||||
"{:'o}", "{:'_o}", "{:'b}", "{:''b}", "{:',B}", "{:'c}", "{:',c}"}) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(spec), 42), format_error,
|
||||
"invalid presentation type for digit grouping")
|
||||
<< spec;
|
||||
}
|
||||
for (auto spec : {"{:'a}", "{:' a}", "{:',a}", "{:'_A}", "{:''A}"}) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime(spec), 4.2), format_error,
|
||||
"invalid presentation type for digit grouping")
|
||||
<< spec;
|
||||
}
|
||||
// L only with the ISO forms.
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:',L}"), 42), format_error,
|
||||
"invalid digit group separator with 'L'");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'_L}"), 42), format_error,
|
||||
"invalid digit group separator with 'L'");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:''.2Lf}"), 4.2), format_error,
|
||||
"invalid digit group separator with 'L'");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:',.4Lf}"), 4.2), format_error,
|
||||
"invalid digit group separator with 'L'");
|
||||
// Other characters after ' are not separators.
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'.}"), 4.2), format_error,
|
||||
"invalid format string");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'-}"), 42), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'''}"), 42), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:',,}"), 42), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:','}"), 42), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:',}"), "abc"), format_error,
|
||||
"invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'_}"), true), format_error,
|
||||
"invalid format specifier");
|
||||
}
|
||||
|
||||
TEST(format_test, precision) {
|
||||
char format_str[buffer_size];
|
||||
safe_sprintf(format_str, "{0:.%u", UINT_MAX);
|
||||
@ -2903,6 +3005,14 @@ TEST(format_test, iso_digit_grouping_locale) {
|
||||
fmt::format(loc, "{:'Lg}", 1234567.0)}) {
|
||||
EXPECT_EQ(s.find('.'), std::string::npos) << s;
|
||||
}
|
||||
// ' followed by space: U+0020 separator, L supplies the decimal point.
|
||||
EXPECT_EQ(fmt::format(loc, "{:' .4Lf}", 1234.5678), "1 234,567 8");
|
||||
EXPECT_EQ(fmt::format(loc, "{:' L}", 1234567), "1 234 567");
|
||||
// ', '_ and '' cannot be combined with L.
|
||||
EXPECT_THROW_MSG((void)fmt::format(loc, runtime("{:',.4Lf}"), 1234.5678),
|
||||
format_error, "invalid digit group separator with 'L'");
|
||||
// Without L the locale is not consulted.
|
||||
EXPECT_EQ(fmt::format(loc, "{:',.4f}", 1234.5678), "1,234.5678");
|
||||
// The format_facet thousands separator is not used either.
|
||||
auto facet_loc =
|
||||
std::locale({}, new fmt::format_facet<std::locale>(",", {1, 2, 3}));
|
||||
|
||||
@ -54,6 +54,14 @@ TEST(iso_grouping_min2_test, four_digit_exception) {
|
||||
EXPECT_EQ(fmt::format("{:04'}", 1), "0001");
|
||||
EXPECT_EQ(fmt::format("{:05'}", 1), iso("00_001"));
|
||||
EXPECT_EQ(fmt::format("{:010'}", 1234567), iso("01_234_567"));
|
||||
// All forms; the comma forms only group the integer part.
|
||||
EXPECT_EQ(fmt::format("{:',}", 1234), "1234");
|
||||
EXPECT_EQ(fmt::format("{:',}", 12345), "12,345");
|
||||
EXPECT_EQ(fmt::format("{:' .4f}", 1234.5678), "1234.5678");
|
||||
EXPECT_EQ(fmt::format("{:' .5f}", 1234.56789), "1234.567 89");
|
||||
EXPECT_EQ(fmt::format("{:'_.5f}", 1234.56789), "1234.56789");
|
||||
EXPECT_EQ(fmt::format("{:04',}", 1), "0001");
|
||||
EXPECT_EQ(fmt::format("{:05''}", 1), "00'001");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:'x}"), 42), format_error,
|
||||
"invalid presentation type for digit grouping");
|
||||
}
|
||||
|
||||
@ -87,15 +87,25 @@ TEST(xchar_test, format) {
|
||||
|
||||
TEST(xchar_test, iso_digit_grouping) {
|
||||
// Wide character types use the single code unit U+202F.
|
||||
EXPECT_EQ(fmt::format(L"{:'}", 1234567), L"1 234 567");
|
||||
EXPECT_EQ(fmt::format(L"{:'.6f}", 1234567.891234), L"1 234 567.891 234");
|
||||
EXPECT_EQ(fmt::format(L"{:'.6e}", 123456.789), L"1.234 568e+05");
|
||||
EXPECT_EQ(fmt::format(L"{:*>12'}", -1234567), L"**-1 234 567");
|
||||
EXPECT_EQ(fmt::format(fmt::runtime(L"{:>12'}"), 1234567), L" 1 234 567");
|
||||
EXPECT_EQ(fmt::format(L"{:'}", 1234567), L"1\u202F234\u202F567");
|
||||
EXPECT_EQ(fmt::format(L"{:'.6f}", 1234567.891234),
|
||||
L"1\u202F234\u202F567.891\u202F234");
|
||||
EXPECT_EQ(fmt::format(L"{:'.6e}", 123456.789), L"1.234\u202F568e+05");
|
||||
EXPECT_EQ(fmt::format(L"{:*>12'}", -1234567), L"**-1\u202F234\u202F567");
|
||||
EXPECT_EQ(fmt::format(fmt::runtime(L"{:>12'}"), 1234567),
|
||||
L" 1\u202F234\u202F567");
|
||||
EXPECT_THROW((void)fmt::format(fmt::runtime(L"{:'x}"), 42),
|
||||
fmt::format_error);
|
||||
EXPECT_EQ(fmt::format(u"{:'}", 1234567), u"1 234 567");
|
||||
EXPECT_EQ(fmt::format(U"{:>10'.1f}", 1234.5), U" 1 234.5");
|
||||
EXPECT_EQ(fmt::format(u"{:'}", 1234567), u"1\u202F234\u202F567");
|
||||
EXPECT_EQ(fmt::format(U"{:>10'.1f}", 1234.5), U" 1\u202F234.5");
|
||||
// The other forms emit a single code unit too.
|
||||
EXPECT_EQ(fmt::format(L"{:' }", 1234567), L"1 234 567");
|
||||
EXPECT_EQ(fmt::format(L"{:',}", 1234567), L"1,234,567");
|
||||
EXPECT_EQ(fmt::format(L"{:'_.3f}", 1234.5), L"1_234.500");
|
||||
EXPECT_EQ(fmt::format(L"{:''}", -1234567), L"-1'234'567");
|
||||
EXPECT_EQ(fmt::format(L"{:010',}", 1234567), L"01,234,567");
|
||||
EXPECT_EQ(fmt::format(u"{:*>10',}", 1234567), u"*1,234,567");
|
||||
EXPECT_EQ(fmt::format(U"{:' .4f}", 1234.5678), U"1 234.567 8");
|
||||
}
|
||||
|
||||
TEST(xchar_test, is_formattable) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user