From 79b79f329ea126c14d150d9b75668ed137b33c42 Mon Sep 17 00:00:00 2001 From: gawain Date: Fri, 1 Mar 2019 20:32:05 +0100 Subject: [PATCH 1/3] Add support for '%' type to output floating point values as a percentage. This helps with compatibility with Python's format strings. --- include/fmt/format-inl.h | 11 ++++++++--- include/fmt/format.h | 31 +++++++++++++++++++++++++++---- test/format-test.cc | 11 ++++++++++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 8f2fe1c9..50760cab 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -539,8 +539,7 @@ struct fixed_stop { void on_exp(int exp) { if (!fixed) return; exp += exp10; - if (exp >= 0) - precision += exp; + if (exp >= 0) precision += exp; } bool operator()(char*, int& size, uint64_t remainder, uint64_t divisor, @@ -656,7 +655,13 @@ void sprintf_format(Double value, internal::buffer& buf, *format_ptr++ = '*'; } if (std::is_same::value) *format_ptr++ = 'L'; - char type = spec.type ? spec.type : 'g'; + + char type = spec.type; + + if (type == '%') + type = 'f'; + else if (type == 0) + type = 'g'; #if FMT_MSC_VER if (type == 'F') { // MSVC's printf doesn't support 'F'. diff --git a/include/fmt/format.h b/include/fmt/format.h index 44584c10..f40c671e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1274,6 +1274,9 @@ FMT_CONSTEXPR void handle_float_type_spec(char spec, Handler&& handler) { case 'F': handler.on_fixed(); break; + case '%': + handler.on_percent(); + break; case 'a': case 'A': handler.on_hex(); @@ -1338,6 +1341,7 @@ class float_type_checker : private ErrorHandler { FMT_CONSTEXPR void on_general() {} FMT_CONSTEXPR void on_exp() {} FMT_CONSTEXPR void on_fixed() {} + FMT_CONSTEXPR void on_percent() {} FMT_CONSTEXPR void on_hex() {} FMT_CONSTEXPR void on_error() { @@ -2642,10 +2646,12 @@ template class basic_writer { struct inf_or_nan_writer { char sign; + bool as_percentage; const char* str; size_t size() const { - return static_cast(INF_SIZE + (sign ? 1 : 0)); + return static_cast(INF_SIZE + (sign ? 1 : 0) + + (as_percentage ? 1 : 0)); } size_t width() const { return size(); } @@ -2653,6 +2659,7 @@ template class basic_writer { if (sign) *it++ = static_cast(sign); it = internal::copy_str( str, str + static_cast(INF_SIZE), it); + if (as_percentage) *it++ = static_cast('%'); } }; @@ -2810,8 +2817,10 @@ struct float_spec_handler { char type; bool upper; bool fixed; + bool as_percentage; - explicit float_spec_handler(char t) : type(t), upper(false), fixed(false) {} + explicit float_spec_handler(char t) + : type(t), upper(false), fixed(false), as_percentage(false) {} void on_general() { if (type == 'G') upper = true; @@ -2826,6 +2835,11 @@ struct float_spec_handler { if (type == 'F') upper = true; } + void on_percent() { + fixed = true; + as_percentage = true; + } + void on_hex() { if (type == 'A') upper = true; } @@ -2854,10 +2868,11 @@ void basic_writer::write_double(T value, const format_specs& spec) { basic_writer& writer; format_specs spec; char sign; + bool as_percentage; void operator()(const char* str) const { - writer.write_padded(spec, inf_or_nan_writer{sign, str}); + writer.write_padded(spec, inf_or_nan_writer{sign, as_percentage, str}); } - } write_inf_or_nan = {*this, spec, sign}; + } write_inf_or_nan = {*this, spec, sign, handler.as_percentage}; // Format NaN and ininity ourselves because sprintf's output is not consistent // across platforms. @@ -2869,11 +2884,19 @@ void basic_writer::write_double(T value, const format_specs& spec) { memory_buffer buffer; int exp = 0; int precision = spec.has_precision() || !spec.type ? spec.precision : 6; + + if (handler.as_percentage) value *= 100.; + bool use_grisu = fmt::internal::use_grisu() && (!spec.type || handler.fixed) && internal::grisu2_format(static_cast(value), buffer, precision, handler.fixed, exp); if (!use_grisu) internal::sprintf_format(value, buffer, spec); + + if (handler.as_percentage) { + buffer.push_back('%'); + --exp; // Adjust decimal place position. + } align_spec as = spec; if (spec.align() == ALIGN_NUMERIC) { if (sign) { diff --git a/test/format-test.cc b/test/format-test.cc index 822557b7..51ce763b 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1207,6 +1207,8 @@ TEST(FormatterTest, Precision) { "precision not allowed for this argument type"); EXPECT_THROW_MSG(format("{0:.2f}", 42ull), format_error, "precision not allowed for this argument type"); + EXPECT_THROW_MSG(format("{0:.2%}", 42), format_error, + "precision not allowed for this argument type"); EXPECT_THROW_MSG(format("{0:3.0}", 'x'), format_error, "precision not allowed for this argument type"); EXPECT_EQ("1.2", format("{0:.2}", 1.2345)); @@ -1440,10 +1442,11 @@ TEST(FormatterTest, FormatConvertibleToLongLong) { TEST(FormatterTest, FormatFloat) { EXPECT_EQ("392.500000", format("{0:f}", 392.5f)); + EXPECT_EQ("12.500000%", format("{0:%}", 0.125f)); } TEST(FormatterTest, FormatDouble) { - check_unknown_types(1.2, "eEfFgGaA", "double"); + check_unknown_types(1.2, "eEfFgGaA%", "double"); EXPECT_EQ("0.0", format("{:}", 0.0)); EXPECT_EQ("0.000000", format("{:f}", 0.0)); EXPECT_EQ("0", format("{:g}", 0.0)); @@ -1452,6 +1455,8 @@ TEST(FormatterTest, FormatDouble) { EXPECT_EQ("392.65", format("{:G}", 392.65)); EXPECT_EQ("392.650000", format("{:f}", 392.65)); EXPECT_EQ("392.650000", format("{:F}", 392.65)); + EXPECT_EQ("12.500000%", format("{:%}", 0.125)); + EXPECT_EQ("12.34%", format("{:.2%}", 0.1234432)); char buffer[BUFFER_SIZE]; safe_sprintf(buffer, "%e", 392.65); EXPECT_EQ(buffer, format("{0:e}", 392.65)); @@ -1473,6 +1478,7 @@ TEST(FormatterTest, FormatNaN) { EXPECT_EQ("nan ", format("{:<7}", nan)); EXPECT_EQ(" nan ", format("{:^7}", nan)); EXPECT_EQ(" nan", format("{:>7}", nan)); + EXPECT_EQ("nan%", format("{:%}", nan)); } TEST(FormatterTest, FormatInfinity) { @@ -1485,6 +1491,7 @@ TEST(FormatterTest, FormatInfinity) { EXPECT_EQ("inf ", format("{:<7}", inf)); EXPECT_EQ(" inf ", format("{:^7}", inf)); EXPECT_EQ(" inf", format("{:>7}", inf)); + EXPECT_EQ("inf%", format("{:%}", inf)); } TEST(FormatterTest, FormatLongDouble) { @@ -1495,6 +1502,8 @@ TEST(FormatterTest, FormatLongDouble) { EXPECT_EQ("392.65", format("{0:G}", 392.65l)); EXPECT_EQ("392.650000", format("{0:f}", 392.65l)); EXPECT_EQ("392.650000", format("{0:F}", 392.65l)); + EXPECT_EQ("12.500000%", format("{:%}", 0.125l)); + EXPECT_EQ("12.34%", format("{:.2%}", 0.1234432l)); char buffer[BUFFER_SIZE]; safe_sprintf(buffer, "%Le", 392.65l); EXPECT_EQ(buffer, format("{0:e}", 392.65l)); From 2e526a664a8b2df428e2f1df790d2f3a40c8e80d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 6 Mar 2019 07:59:23 -0800 Subject: [PATCH 2/3] Fix handling of output iterator in ranges --- include/fmt/ranges.h | 51 ++++++++++++++++++++------------------------ test/ranges-test.cc | 15 +++++++++---- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 5145fc06..fd8d0203 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -55,21 +55,22 @@ struct formatting_tuple : formatting_base { namespace internal { template -void copy(const RangeT& range, OutputIterator out) { +OutputIterator copy(const RangeT& range, OutputIterator out) { for (auto it = range.begin(), end = range.end(); it != end; ++it) *out++ = *it; + return out; } template -void copy(const char* str, OutputIterator out) { - const char* p_curr = str; - while (*p_curr) { - *out++ = *p_curr++; - } +OutputIterator copy(const char* str, OutputIterator out) { + while (*str) *out++ = *str++; + return out; } -template void copy(char ch, OutputIterator out) { +template +OutputIterator copy(char ch, OutputIterator out) { *out++ = ch; + return out; } /// Return true value if T has std::string interface, like std::string_view. @@ -211,12 +212,12 @@ struct formatter< if (formatting.add_prepostfix_space) { *out++ = ' '; } - internal::copy(formatting.delimiter, out); + out = internal::copy(formatting.delimiter, out); } - format_to(out, - internal::format_str_quoted( - (formatting.add_delimiter_spaces && i > 0), v), - v); + out = format_to(out, + internal::format_str_quoted( + (formatting.add_delimiter_spaces && i > 0), v), + v); ++i; } @@ -268,30 +269,24 @@ struct formatter typename FormatContext::iterator format(const RangeT& values, FormatContext& ctx) { - auto out = ctx.out(); - internal::copy(formatting.prefix, out); + auto out = internal::copy(formatting.prefix, ctx.out()); std::size_t i = 0; for (auto it = values.begin(), end = values.end(); it != end; ++it) { if (i > 0) { - if (formatting.add_prepostfix_space) { - *out++ = ' '; - } - internal::copy(formatting.delimiter, out); + if (formatting.add_prepostfix_space) *out++ = ' '; + out = internal::copy(formatting.delimiter, out); } - format_to(out, - internal::format_str_quoted( - (formatting.add_delimiter_spaces && i > 0), *it), - *it); + out = format_to(out, + internal::format_str_quoted( + (formatting.add_delimiter_spaces && i > 0), *it), + *it); if (++i > formatting.range_length_limit) { - format_to(out, " ... "); + out = format_to(out, " ... "); break; } } - if (formatting.add_prepostfix_space) { - *out++ = ' '; - } - internal::copy(formatting.postfix, out); - return ctx.out(); + if (formatting.add_prepostfix_space) *out++ = ' '; + return internal::copy(formatting.postfix, out); } }; diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 97359973..b8990717 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -39,14 +39,14 @@ TEST(RangesTest, FormatMap) { } TEST(RangesTest, FormatPair) { - std::pair pa1{42, 3.14159265358979f}; - EXPECT_EQ("(42, 3.14159)", fmt::format("{}", pa1)); + std::pair pa1{42, 1.5f}; + EXPECT_EQ("(42, 1.5)", fmt::format("{}", pa1)); } TEST(RangesTest, FormatTuple) { - std::tuple tu1{42, 3.14159265358979f, + std::tuple tu1{42, 1.5f, "this is tuple", 'i'}; - EXPECT_EQ("(42, 3.14159, \"this is tuple\", 'i')", fmt::format("{}", tu1)); + EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1)); } struct my_struct { @@ -80,5 +80,12 @@ TEST(RangesTest, FormatStruct) { EXPECT_EQ("(13, \"my struct\")", fmt::format("{}", mst)); } +TEST(RangesTest, FormatTo) { + char buf[10]; + auto end = fmt::format_to(buf, "{}", std::vector{1, 2, 3}); + *end = '\0'; + EXPECT_STREQ(buf, "{1, 2, 3}"); +} + #endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG > // 201402L && _MSC_VER >= 1910) From 8f7780a4f6c26284ea29b4c0c5a00afc1e175dd6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 6 Mar 2019 09:23:16 -0800 Subject: [PATCH 3/3] Correct comment --- include/fmt/ranges.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index fd8d0203..9201433e 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -1,4 +1,4 @@ -// Formatting library for C++ - the core API +// Formatting library for C++ - experimental range support // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved.