Skip to content

Commit ad72687

Browse files
authored
Merge pull request #1163 from cppalliance/locale
2 parents a49f1a3 + cfd667b commit ad72687

File tree

6 files changed

+142
-9
lines changed

6 files changed

+142
-9
lines changed

doc/modules/ROOT/pages/format.adoc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,29 @@ Boost.Decimal supports formatting with both `<format>` (when C++20 and header ar
1515

1616
Format is supported when using C++20 and a compiler with appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.40
1717

18+
=== Locale Modifier
19+
20+
If a format string begins with "L" the current global locale will be applied.
21+
This can be set as so:
22+
23+
[source, c++]
24+
----
25+
// Locale can be any valid locale that is installed on your system
26+
const char* locale = "de_DE.UTF-8";
27+
const std::locale a(locale);
28+
std::locale::global(a);
29+
----
30+
31+
=== Sign Modifier
32+
33+
|===
34+
| Modifier | Format
35+
| "+" | All values will have a sign
36+
| "-" | Only negative values have a sign
37+
| " " | Positive values have a leading space,
38+
Negative have a minus sign
39+
|===
40+
1841
=== Type Modifiers
1942

2043
The following type modifiers are the same as those used by built-in floating point values:
@@ -47,6 +70,12 @@ For example with `{:10.3e}`:
4770

4871
Note the space at the front of these string to keep with width at 10 characters
4972

73+
=== Putting it All Together
74+
75+
The appropriate order for the full format specifier is:
76+
77+
Locale, Sign, Padding, Precision, Type
78+
5079
=== Examples
5180

5281
The example is padding modifiers can be done like so

include/boost/decimal/detail/locale_conversion.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ inline void convert_string_to_c_locale(char* buffer) noexcept
8484
convert_string_to_c_locale(buffer, std::locale());
8585
}
8686

87-
inline int convert_pointer_pair_to_local_locale(char* first, char* last, const std::locale& loc) noexcept
87+
inline int convert_pointer_pair_to_local_locale(char* first, const char* last, const std::locale& loc) noexcept
8888
{
8989
const std::numpunct<char>& np = std::use_facet<std::numpunct<char>>(loc);
9090

@@ -187,7 +187,7 @@ inline int convert_pointer_pair_to_local_locale(char* first, char* last, const s
187187
# pragma GCC diagnostic pop
188188
#endif
189189

190-
inline int convert_pointer_pair_to_local_locale(char* first, char* last)
190+
inline int convert_pointer_pair_to_local_locale(char* first, const char* last)
191191
{
192192
const auto loc {std::locale()};
193193
return convert_pointer_pair_to_local_locale(first, last, loc);

include/boost/decimal/fmt_format.hpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <fmt/format.h>
1313
#include <fmt/base.h>
1414
#include <boost/decimal/detail/config.hpp>
15+
#include <boost/decimal/detail/locale_conversion.hpp>
1516
#include <boost/decimal/charconv.hpp>
1617
#include <algorithm>
1718
#include <format>
@@ -40,11 +41,19 @@ constexpr auto parse_impl(ParseContext &ctx)
4041
boost::decimal::chars_format fmt = boost::decimal::chars_format::general;
4142
bool is_upper = false;
4243
int padding_digits = 0;
44+
bool use_locale = false;
4345
auto it {ctx.begin()};
4446

4547
if (it == nullptr)
4648
{
47-
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, it);
49+
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, use_locale, it);
50+
}
51+
52+
// Check for the locale character
53+
if (*it == 'L')
54+
{
55+
use_locale = true;
56+
++it;
4857
}
4958

5059
// Check for a sign character
@@ -156,7 +165,7 @@ constexpr auto parse_impl(ParseContext &ctx)
156165
BOOST_DECIMAL_THROW_EXCEPTION(std::logic_error("Expected '}' in format string")); // LCOV_EXCL_LINE
157166
}
158167

159-
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, it);
168+
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, use_locale, it);
160169
}
161170

162171
template <typename T>
@@ -167,12 +176,14 @@ struct formatter
167176
int padding_digits;
168177
int ctx_precision;
169178
bool is_upper;
179+
bool use_locale;
170180

171181
constexpr formatter() : sign{sign_option::minus},
172182
fmt{chars_format::general},
173183
padding_digits{0},
174184
ctx_precision{6},
175-
is_upper{false} {}
185+
is_upper{false},
186+
use_locale{false} {}
176187

177188
constexpr auto parse(fmt::format_parse_context &ctx)
178189
{
@@ -183,8 +194,9 @@ struct formatter
183194
is_upper = std::get<2>(res);
184195
padding_digits = std::get<3>(res);
185196
sign = std::get<4>(res);
197+
use_locale = std::get<5>(res);
186198

187-
return std::get<5>(res);
199+
return std::get<6>(res);
188200
}
189201

190202
template <typename FormatContext>
@@ -254,6 +266,16 @@ struct formatter
254266
s.insert(s.begin() + static_cast<std::size_t>(has_sign), static_cast<std::size_t>(padding_digits) - s.size(), '0');
255267
}
256268

269+
if (use_locale)
270+
{
271+
// We need approximately 1/3 more space in order to insert the thousands separators,
272+
// but after we have done our processing we need to shrink the string back down
273+
const auto initial_length {s.length()};
274+
s.resize(s.length() * 4 / 3 + 1);
275+
const auto offset {static_cast<std::size_t>(convert_pointer_pair_to_local_locale(const_cast<char*>(s.data()), s.data() + s.length()))};
276+
s.resize(initial_length + offset);
277+
}
278+
257279
return fmt::format_to(ctx.out(), "{}", s);
258280
}
259281
};

include/boost/decimal/format.hpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#define BOOST_DECIMAL_HAS_FORMAT_SUPPORT
1313

1414
#include <boost/decimal/detail/config.hpp>
15+
#include <boost/decimal/detail/locale_conversion.hpp>
1516
#include <boost/decimal/charconv.hpp>
1617
#include <algorithm>
1718
#include <format>
@@ -47,6 +48,14 @@ constexpr auto parse_impl(ParseContext &ctx)
4748
boost::decimal::chars_format fmt = boost::decimal::chars_format::general;
4849
bool is_upper = false;
4950
int padding_digits = 0;
51+
bool use_locale = false;
52+
53+
// Check for the locale character
54+
if (*it == 'L')
55+
{
56+
use_locale = true;
57+
++it;
58+
}
5059

5160
// Check for a sign character
5261
if (it != ctx.end())
@@ -148,7 +157,7 @@ constexpr auto parse_impl(ParseContext &ctx)
148157
BOOST_DECIMAL_THROW_EXCEPTION(std::format_error("Expected '}' in format string")); // LCOV_EXCL_LINE
149158
}
150159

151-
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, it);
160+
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, use_locale, it);
152161
}
153162

154163
} // Namespace boost::decimal::detail
@@ -163,12 +172,14 @@ struct formatter<T>
163172
int ctx_precision;
164173
int padding_digits;
165174
bool is_upper;
175+
bool use_locale;
166176

167177
constexpr formatter() : fmt(boost::decimal::chars_format::general),
168178
sign(boost::decimal::detail::format_sign_option::minus),
169179
ctx_precision(6),
170180
padding_digits(0),
171-
is_upper(false)
181+
is_upper(false),
182+
use_locale(false)
172183
{}
173184

174185
constexpr auto parse(format_parse_context &ctx)
@@ -180,8 +191,9 @@ struct formatter<T>
180191
is_upper = std::get<2>(res);
181192
padding_digits = std::get<3>(res);
182193
sign = std::get<4>(res);
194+
use_locale = std::get<5>(res);
183195

184-
return std::get<5>(res);
196+
return std::get<6>(res);
185197
}
186198

187199
template <typename FormatContext>
@@ -254,6 +266,16 @@ struct formatter<T>
254266
s.insert(s.begin() + static_cast<std::size_t>(has_sign), static_cast<std::size_t>(padding_digits) - s.size(), '0');
255267
}
256268

269+
if (use_locale)
270+
{
271+
// We need approximately 1/3 more space in order to insert the thousands separators,
272+
// but after we have done our processing we need to shrink the string back down
273+
const auto initial_length {s.length()};
274+
s.resize(s.length() * 4 / 3 + 1);
275+
const auto offset {static_cast<std::size_t>(convert_pointer_pair_to_local_locale(const_cast<char*>(s.data()), s.data() + s.length()))};
276+
s.resize(initial_length + offset);
277+
}
278+
257279
return std::format_to(ctx.out(), "{}", s);
258280
}
259281
};

test/test_format.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,25 @@ void test_cohort_preservation()
246246
}
247247
}
248248

249+
template <typename T>
250+
void test_locale_conversion(const char* locale, const std::string& result)
251+
{
252+
try
253+
{
254+
const std::locale a(locale);
255+
std::locale::global(a);
256+
257+
const T value {112289, -2};
258+
BOOST_TEST_EQ(std::format("{:L.2f}", value), result);
259+
}
260+
// LCOV_EXCL_START
261+
catch (...)
262+
{
263+
std::cerr << "Test not run" << std::endl;
264+
}
265+
// LCOV_EXCL_STOP
266+
}
267+
249268
int main()
250269
{
251270
test_general<decimal32_t>();
@@ -287,6 +306,17 @@ int main()
287306
test_cohort_preservation<decimal64_t>();
288307
test_cohort_preservation<decimal128_t>();
289308

309+
#if !(defined(__GNUC__) && __GNUC__ >= 5 && defined(__APPLE__)) && !defined(BOOST_DECIMAL_QEMU_TEST) && !defined(BOOST_DECIMAL_DISABLE_EXCEPTIONS) && !defined(_MSC_VER)
310+
311+
test_locale_conversion<decimal32_t>("en_US.UTF-8", "1,122.89");
312+
test_locale_conversion<decimal32_t>("de_DE.UTF-8", "1.122,89");
313+
314+
#if (defined(__clang__) && __clang_major__ > 9) || (defined(__GNUC__) && __GNUC__ > 9)
315+
test_locale_conversion<decimal32_t>("fr_FR.UTF-8", "1 122,89");
316+
#endif
317+
318+
#endif
319+
290320
return boost::report_errors();
291321
}
292322

test/test_format_fmtlib.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,25 @@ void test_cohort_preservation()
270270
}
271271
}
272272

273+
template <typename T>
274+
void test_locale_conversion(const char* locale, const std::string& result)
275+
{
276+
try
277+
{
278+
const std::locale a(locale);
279+
std::locale::global(a);
280+
281+
const T value {112289, -2};
282+
BOOST_TEST_EQ(fmt::format("{:L.2f}", value), result);
283+
}
284+
// LCOV_EXCL_START
285+
catch (...)
286+
{
287+
std::cerr << "Test not run" << std::endl;
288+
}
289+
// LCOV_EXCL_STOP
290+
}
291+
273292
#ifdef _MSC_VER
274293
#pragma warning(pop)
275294
#endif
@@ -315,6 +334,17 @@ int main()
315334
test_cohort_preservation<decimal64_t>();
316335
test_cohort_preservation<decimal128_t>();
317336

337+
#if !(defined(__GNUC__) && __GNUC__ >= 5 && defined(__APPLE__)) && !defined(BOOST_DECIMAL_QEMU_TEST) && !defined(BOOST_DECIMAL_DISABLE_EXCEPTIONS) && !defined(_MSC_VER)
338+
339+
test_locale_conversion<decimal32_t>("en_US.UTF-8", "1,122.89");
340+
test_locale_conversion<decimal32_t>("de_DE.UTF-8", "1.122,89");
341+
342+
#if (defined(__clang__) && __clang_major__ > 9) || (defined(__GNUC__) && __GNUC__ > 9)
343+
test_locale_conversion<decimal32_t>("fr_FR.UTF-8", "1 122,89");
344+
#endif
345+
346+
#endif
347+
318348
return boost::report_errors();
319349
}
320350

0 commit comments

Comments
 (0)