Skip to content

Commit a49f1a3

Browse files
authored
Merge pull request #1161 from cppalliance/1156
Add cohort preservation options to {fmt} and <format>
2 parents 5cd9440 + 46126bd commit a49f1a3

File tree

8 files changed

+197
-41
lines changed

8 files changed

+197
-41
lines changed

doc/modules/ROOT/pages/format.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ The following type modifiers are the same as those used by built-in floating poi
2424
| "g" or "G" | General
2525
| "e" or "E" | Scientific
2626
| "f" | Fixed
27-
| "a" or "A" | Hex
27+
| "x" or "X" | Hex
28+
| "a" or "A" | Cohort Preserving Scientific
2829
|===
2930

3031
Example usage for scientific format would be: `{:e}`

examples/fmt_format.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,37 @@
33
// https://www.boost.org/LICENSE_1_0.txt
44

55
#include <boost/decimal.hpp>
6+
#include <boost/decimal/fmt_format.hpp>
67
#include <iostream>
78

89
#if defined(BOOST_DECIMAL_HAS_FMTLIB_SUPPORT) && defined(BOOST_DECIMAL_TEST_FMT)
910

10-
#include <boost/decimal/fmt_format.hpp>
11-
1211
int main()
1312
{
1413
constexpr boost::decimal::decimal64_t val1 {314, -2};
1514
constexpr boost::decimal::decimal32_t val2 {3141, -3};
1615

16+
// The easiest is no specification which is general format
17+
// Given these values they will print in fixed format
18+
std::cout << "Default Format:\n";
19+
std::cout << fmt::format("{}", val1) << '\n';
20+
std::cout << fmt::format("{}", val2) << "\n\n";
21+
22+
// Next we can add a type modifier to get scientific formatting
23+
std::cout << "Scientific Format:\n";
24+
std::cout << fmt::format("{:e}", val1) << '\n';
25+
std::cout << fmt::format("{:e}", val2) << "\n\n";
26+
27+
// Next we can add a type modifier to get scientific formatting
28+
// Here this gives one digit of precision rounded according to current rounding mode
29+
std::cout << "Scientific Format with Specified Precision:\n";
30+
std::cout << fmt::format("{:.1e}", val1) << '\n';
31+
std::cout << fmt::format("{:.1e}", val2) << "\n\n";
32+
33+
// This combines the padding modifier (10), precision (3 digits), and a type modifier (e)
34+
std::cout << "Scientific Format with Specified Precision and Padding:\n";
1735
std::cout << fmt::format("{:10.3e}", val1) << '\n';
18-
std::cout << fmt::format("{:10.3e}", val2) << std::endl;
36+
std::cout << fmt::format("{:10.3e}", val2) << '\n';
1937

2038
return 0;
2139
}

include/boost/decimal/charconv.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,7 +1222,7 @@ constexpr auto to_chars_impl(char* first, char* last, const TargetDecimalType& v
12221222
auto abs_value = abs(value);
12231223
constexpr auto max_fractional_value = decimal_val_v<TargetDecimalType> < 64 ? TargetDecimalType{1, 7} :
12241224
decimal_val_v<TargetDecimalType> < 128 ? TargetDecimalType{1, 16} :
1225-
TargetDecimalType{1, 34};
1225+
TargetDecimalType{1, 34};
12261226

12271227
constexpr auto min_fractional_value = TargetDecimalType{1, -4};
12281228

@@ -1232,7 +1232,7 @@ constexpr auto to_chars_impl(char* first, char* last, const TargetDecimalType& v
12321232
switch (fmt)
12331233
{
12341234
case chars_format::general:
1235-
if (abs_value >= 1 && abs_value < max_fractional_value)
1235+
if (abs_value >= min_fractional_value && abs_value < max_fractional_value)
12361236
{
12371237
return to_chars_fixed_impl(first, last, value, fmt);
12381238
}

include/boost/decimal/fmt_format.hpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,31 @@ constexpr auto parse_impl(ParseContext &ctx)
117117
fmt = chars_format::scientific;
118118
break;
119119

120-
case 'A':
120+
case 'X':
121121
is_upper = true;
122122
fmt = chars_format::hex;
123123
break;
124-
case 'a':
124+
case 'x':
125125
fmt = chars_format::hex;
126126
break;
127+
128+
case 'A':
129+
if (ctx_precision != -1)
130+
{
131+
BOOST_DECIMAL_THROW_EXCEPTION(std::logic_error("Cohort preservation is mutually exclusive with precision"));
132+
}
133+
134+
is_upper = true;
135+
fmt = chars_format::cohort_preserving_scientific;
136+
break;
137+
case 'a':
138+
if (ctx_precision != -1)
139+
{
140+
BOOST_DECIMAL_THROW_EXCEPTION(std::logic_error("Cohort preservation is mutually exclusive with precision"));
141+
}
142+
143+
fmt = chars_format::cohort_preserving_scientific;
144+
break;
127145
// LCOV_EXCL_START
128146
default:
129147
BOOST_DECIMAL_THROW_EXCEPTION(std::logic_error("Invalid format specifier"));

include/boost/decimal/format.hpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
// Default :g
2525
// Fixed :f
2626
// Scientific :e
27-
// Hex :a
27+
// Hex :x
28+
// Cohort Preserving :a
2829
//
2930
// Capital letter for any of the above leads to all characters being uppercase
3031

@@ -114,12 +115,25 @@ constexpr auto parse_impl(ParseContext &ctx)
114115
fmt = chars_format::scientific;
115116
break;
116117

118+
case 'X':
119+
is_upper = true;
120+
[[fallthrough]];
121+
case 'x':
122+
fmt = chars_format::hex;
123+
break;
124+
117125
case 'A':
118126
is_upper = true;
119127
[[fallthrough]];
120128
case 'a':
121-
fmt = chars_format::hex;
129+
if (ctx_precision != -1)
130+
{
131+
BOOST_DECIMAL_THROW_EXCEPTION(std::format_error("Cohort preservation is mutually exclusive with precision"));
132+
}
133+
134+
fmt = chars_format::cohort_preserving_scientific;
122135
break;
136+
123137
// LCOV_EXCL_START
124138
default:
125139
BOOST_DECIMAL_THROW_EXCEPTION(std::format_error("Invalid format specifier"));

test/github_issue_988.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ void test()
2828
const auto r = to_chars(buffer, buffer + sizeof(buffer), decimal_value);
2929
BOOST_TEST(r);
3030
*r.ptr = '\0';
31-
BOOST_TEST_CSTR_EQ(buffer, "-9.9999999999984e-01");
31+
BOOST_TEST_CSTR_EQ(buffer, "-0.99999999999984");
3232
}
3333

3434
void general_precision_test()

test/test_format.cpp

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include <boost/decimal.hpp>
66
#include <boost/core/lightweight_test.hpp>
77
#include <limits>
8+
#include <array>
9+
#include <cctype>
10+
#include <string>
811

912
using namespace boost::decimal;
1013

@@ -173,21 +176,21 @@ void test_scientific()
173176
template <typename T>
174177
void test_hex()
175178
{
176-
BOOST_TEST_EQ(std::format("{:.0a}", T {0}), "0p+00");
177-
BOOST_TEST_EQ(std::format("{:.3A}", T {0}), "0.000P+00");
178-
BOOST_TEST_EQ(std::format("{:a}", std::numeric_limits<T>::infinity()), "inf");
179-
BOOST_TEST_EQ(std::format("{:a}", -std::numeric_limits<T>::infinity()), "-inf");
180-
BOOST_TEST_EQ(std::format("{:a}", std::numeric_limits<T>::quiet_NaN()), "nan");
181-
BOOST_TEST_EQ(std::format("{:a}", -std::numeric_limits<T>::quiet_NaN()), "-nan(ind)");
182-
BOOST_TEST_EQ(std::format("{:a}", std::numeric_limits<T>::signaling_NaN()), "nan(snan)");
183-
BOOST_TEST_EQ(std::format("{:a}", -std::numeric_limits<T>::signaling_NaN()), "-nan(snan)");
184-
185-
BOOST_TEST_EQ(std::format("{:A}", std::numeric_limits<T>::infinity()), "INF");
186-
BOOST_TEST_EQ(std::format("{:A}", -std::numeric_limits<T>::infinity()), "-INF");
187-
BOOST_TEST_EQ(std::format("{:A}", std::numeric_limits<T>::quiet_NaN()), "NAN");
188-
BOOST_TEST_EQ(std::format("{:A}", -std::numeric_limits<T>::quiet_NaN()), "-NAN(IND)");
189-
BOOST_TEST_EQ(std::format("{:A}", std::numeric_limits<T>::signaling_NaN()), "NAN(SNAN)");
190-
BOOST_TEST_EQ(std::format("{:A}", -std::numeric_limits<T>::signaling_NaN()), "-NAN(SNAN)");
179+
BOOST_TEST_EQ(std::format("{:.0x}", T {0}), "0p+00");
180+
BOOST_TEST_EQ(std::format("{:.3X}", T {0}), "0.000P+00");
181+
BOOST_TEST_EQ(std::format("{:x}", std::numeric_limits<T>::infinity()), "inf");
182+
BOOST_TEST_EQ(std::format("{:x}", -std::numeric_limits<T>::infinity()), "-inf");
183+
BOOST_TEST_EQ(std::format("{:x}", std::numeric_limits<T>::quiet_NaN()), "nan");
184+
BOOST_TEST_EQ(std::format("{:x}", -std::numeric_limits<T>::quiet_NaN()), "-nan(ind)");
185+
BOOST_TEST_EQ(std::format("{:x}", std::numeric_limits<T>::signaling_NaN()), "nan(snan)");
186+
BOOST_TEST_EQ(std::format("{:x}", -std::numeric_limits<T>::signaling_NaN()), "-nan(snan)");
187+
188+
BOOST_TEST_EQ(std::format("{:X}", std::numeric_limits<T>::infinity()), "INF");
189+
BOOST_TEST_EQ(std::format("{:X}", -std::numeric_limits<T>::infinity()), "-INF");
190+
BOOST_TEST_EQ(std::format("{:X}", std::numeric_limits<T>::quiet_NaN()), "NAN");
191+
BOOST_TEST_EQ(std::format("{:X}", -std::numeric_limits<T>::quiet_NaN()), "-NAN(IND)");
192+
BOOST_TEST_EQ(std::format("{:X}", std::numeric_limits<T>::signaling_NaN()), "NAN(SNAN)");
193+
BOOST_TEST_EQ(std::format("{:X}", -std::numeric_limits<T>::signaling_NaN()), "-NAN(SNAN)");
191194
}
192195

193196
template <typename T>
@@ -197,6 +200,52 @@ void test_with_string()
197200
BOOST_TEST_EQ(std::format("Height is: {} meters", T {2}), "Height is: 2 meters");
198201
}
199202

203+
template <typename T>
204+
void test_cohort_preservation()
205+
{
206+
const std::array<T, 7> decimals = {
207+
T {5, 4},
208+
T {50, 3},
209+
T {500, 2},
210+
T {5000, 1},
211+
T {50000, 0},
212+
T {500000, -1},
213+
T {5000000, -2},
214+
};
215+
216+
const std::array<const char*, 7> result_strings = {
217+
"5e+04",
218+
"5.0e+04",
219+
"5.00e+04",
220+
"5.000e+04",
221+
"5.0000e+04",
222+
"5.00000e+04",
223+
"5.000000e+04",
224+
};
225+
226+
for (std::size_t i {}; i < decimals.size(); ++i)
227+
{
228+
BOOST_TEST_CSTR_EQ(std::format("{:a}", decimals[i]).c_str(), result_strings[i]);
229+
230+
std::string s {result_strings[i]};
231+
232+
#ifdef _MSC_VER
233+
# pragma warning(push)
234+
# pragma warning(disable : 4244)
235+
#endif
236+
237+
std::transform(s.begin(), s.end(), s.begin(),
238+
[](unsigned char c)
239+
{ return std::toupper(c); });
240+
241+
#ifdef _MSC_VER
242+
# pragma warning(pop)
243+
#endif
244+
245+
BOOST_TEST_CSTR_EQ(std::format("{:A}", decimals[i]).c_str(), s.c_str());
246+
}
247+
}
248+
200249
int main()
201250
{
202251
test_general<decimal32_t>();
@@ -234,6 +283,10 @@ int main()
234283
test_with_string<decimal128_t>();
235284
test_with_string<decimal_fast128_t>();
236285

286+
test_cohort_preservation<decimal32_t>();
287+
test_cohort_preservation<decimal64_t>();
288+
test_cohort_preservation<decimal128_t>();
289+
237290
return boost::report_errors();
238291
}
239292

test/test_format_fmtlib.cpp

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#include <boost/decimal/fmt_format.hpp>
1313
#include <boost/core/lightweight_test.hpp>
1414
#include <limits>
15+
#include <array>
16+
#include <cctype>
17+
#include <string>
1518

1619
using namespace boost::decimal;
1720

@@ -198,21 +201,21 @@ void test_scientific()
198201
template <typename T>
199202
void test_hex()
200203
{
201-
BOOST_TEST_EQ(fmt::format("{:.0a}", T {0}), "0p+00");
202-
BOOST_TEST_EQ(fmt::format("{:.3A}", T {0}), "0.000P+00");
203-
BOOST_TEST_EQ(fmt::format("{:a}", std::numeric_limits<T>::infinity()), "inf");
204-
BOOST_TEST_EQ(fmt::format("{:a}", -std::numeric_limits<T>::infinity()), "-inf");
205-
BOOST_TEST_EQ(fmt::format("{:a}", std::numeric_limits<T>::quiet_NaN()), "nan");
206-
BOOST_TEST_EQ(fmt::format("{:a}", -std::numeric_limits<T>::quiet_NaN()), "-nan(ind)");
207-
BOOST_TEST_EQ(fmt::format("{:a}", std::numeric_limits<T>::signaling_NaN()), "nan(snan)");
208-
BOOST_TEST_EQ(fmt::format("{:a}", -std::numeric_limits<T>::signaling_NaN()), "-nan(snan)");
209-
210-
BOOST_TEST_EQ(fmt::format("{:A}", std::numeric_limits<T>::infinity()), "INF");
211-
BOOST_TEST_EQ(fmt::format("{:A}", -std::numeric_limits<T>::infinity()), "-INF");
212-
BOOST_TEST_EQ(fmt::format("{:A}", std::numeric_limits<T>::quiet_NaN()), "NAN");
213-
BOOST_TEST_EQ(fmt::format("{:A}", -std::numeric_limits<T>::quiet_NaN()), "-NAN(IND)");
214-
BOOST_TEST_EQ(fmt::format("{:A}", std::numeric_limits<T>::signaling_NaN()), "NAN(SNAN)");
215-
BOOST_TEST_EQ(fmt::format("{:A}", -std::numeric_limits<T>::signaling_NaN()), "-NAN(SNAN)");
204+
BOOST_TEST_EQ(fmt::format("{:.0x}", T {0}), "0p+00");
205+
BOOST_TEST_EQ(fmt::format("{:.3X}", T {0}), "0.000P+00");
206+
BOOST_TEST_EQ(fmt::format("{:x}", std::numeric_limits<T>::infinity()), "inf");
207+
BOOST_TEST_EQ(fmt::format("{:x}", -std::numeric_limits<T>::infinity()), "-inf");
208+
BOOST_TEST_EQ(fmt::format("{:x}", std::numeric_limits<T>::quiet_NaN()), "nan");
209+
BOOST_TEST_EQ(fmt::format("{:x}", -std::numeric_limits<T>::quiet_NaN()), "-nan(ind)");
210+
BOOST_TEST_EQ(fmt::format("{:x}", std::numeric_limits<T>::signaling_NaN()), "nan(snan)");
211+
BOOST_TEST_EQ(fmt::format("{:x}", -std::numeric_limits<T>::signaling_NaN()), "-nan(snan)");
212+
213+
BOOST_TEST_EQ(fmt::format("{:X}", std::numeric_limits<T>::infinity()), "INF");
214+
BOOST_TEST_EQ(fmt::format("{:X}", -std::numeric_limits<T>::infinity()), "-INF");
215+
BOOST_TEST_EQ(fmt::format("{:X}", std::numeric_limits<T>::quiet_NaN()), "NAN");
216+
BOOST_TEST_EQ(fmt::format("{:X}", -std::numeric_limits<T>::quiet_NaN()), "-NAN(IND)");
217+
BOOST_TEST_EQ(fmt::format("{:X}", std::numeric_limits<T>::signaling_NaN()), "NAN(SNAN)");
218+
BOOST_TEST_EQ(fmt::format("{:X}", -std::numeric_limits<T>::signaling_NaN()), "-NAN(SNAN)");
216219
}
217220

218221
template <typename T>
@@ -222,6 +225,51 @@ void test_with_string()
222225
BOOST_TEST_EQ(fmt::format("Height is: {} meters", T {2}), "Height is: 2 meters");
223226
}
224227

228+
template <typename T>
229+
void test_cohort_preservation()
230+
{
231+
const std::array<T, 7> decimals = {
232+
T {5, 4},
233+
T {50, 3},
234+
T {500, 2},
235+
T {5000, 1},
236+
T {50000, 0},
237+
T {500000, -1},
238+
T {5000000, -2},
239+
};
240+
241+
const std::array<const char*, 7> result_strings = {
242+
"5e+04",
243+
"5.0e+04",
244+
"5.00e+04",
245+
"5.000e+04",
246+
"5.0000e+04",
247+
"5.00000e+04",
248+
"5.000000e+04",
249+
};
250+
251+
for (std::size_t i {}; i < decimals.size(); ++i)
252+
{
253+
BOOST_TEST_CSTR_EQ(fmt::format("{:a}", decimals[i]).c_str(), result_strings[i]);
254+
255+
#ifdef _MSC_VER
256+
# pragma warning(push)
257+
# pragma warning(disable : 4244)
258+
#endif
259+
260+
std::string s {result_strings[i]};
261+
std::transform(s.begin(), s.end(), s.begin(),
262+
[](unsigned char c)
263+
{ return std::toupper(c); });
264+
265+
#ifdef _MSC_VER
266+
# pragma warning(pop)
267+
#endif
268+
269+
BOOST_TEST_CSTR_EQ(fmt::format("{:A}", decimals[i]).c_str(), s.c_str());
270+
}
271+
}
272+
225273
#ifdef _MSC_VER
226274
#pragma warning(pop)
227275
#endif
@@ -263,6 +311,10 @@ int main()
263311
test_with_string<decimal128_t>();
264312
test_with_string<decimal_fast128_t>();
265313

314+
test_cohort_preservation<decimal32_t>();
315+
test_cohort_preservation<decimal64_t>();
316+
test_cohort_preservation<decimal128_t>();
317+
266318
return boost::report_errors();
267319
}
268320

0 commit comments

Comments
 (0)