feat(reporter): allow stringifying exponent for counters in console (fixes #818) (#2305)
Adds benchmark::Counter::kStringifyExponent flag to format user counters with explicit exponents instead of SI prefixes in console output. Updates HumanReadableNumber to support stringifying the exponent (supporting *2^ for binary base 1024 and e for base 1000). Also updates Python bindings, user guide documentation, and test suite.
Co-authored-by: Roman Lebedev <lebedev.ri@gmail.com>
diff --git a/bindings/python/google_benchmark/benchmark.cc b/bindings/python/google_benchmark/benchmark.cc
index ccd7eb5..f60b0ce 100644
--- a/bindings/python/google_benchmark/benchmark.cc
+++ b/bindings/python/google_benchmark/benchmark.cc
@@ -122,6 +122,7 @@
Counter::Flags::kIsIterationInvariantRate)
.value("kAvgIterations", Counter::Flags::kAvgIterations)
.value("kAvgIterationsRate", Counter::Flags::kAvgIterationsRate)
+ .value("kStringifyExponent", Counter::Flags::kStringifyExponent)
.value("kInvert", Counter::Flags::kInvert)
.export_values();
diff --git a/docs/user_guide.md b/docs/user_guide.md
index 9e46a51..2b6e800 100644
--- a/docs/user_guide.md
+++ b/docs/user_guide.md
@@ -1105,6 +1105,12 @@
// This says that we process with the rate of state.range(0) bytes every iteration:
state.counters["BytesProcessed"] = Counter(state.range(0), benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::OneK::kIs1024);
+
+ // Set the counter to stringify the exponent explicitly without SI prefixes (e.g. 1.2345e-04):
+ state.counters["Error"] = Counter(absError, benchmark::Counter::kStringifyExponent);
+
+ // Stringified exponents can also be combined with rates:
+ state.counters["ErrorRate"] = Counter(absError, benchmark::Counter::kStringifyExponent | benchmark::Counter::kIsRate);
```
You can use `insert()` with `std::initializer_list`:
diff --git a/include/benchmark/counter.h b/include/benchmark/counter.h
index 8db5d8d..20fb368 100644
--- a/include/benchmark/counter.h
+++ b/include/benchmark/counter.h
@@ -39,6 +39,7 @@
kIsIterationInvariantRate = kIsRate | kIsIterationInvariant,
kAvgIterations = 1 << 3,
kAvgIterationsRate = kIsRate | kAvgIterations,
+ kStringifyExponent = 1 << 4,
kInvert = 1 << 31
};
diff --git a/src/console_reporter.cc b/src/console_reporter.cc
index 84fe99d..7550434 100644
--- a/src/console_reporter.cc
+++ b/src/console_reporter.cc
@@ -192,7 +192,9 @@
s = StrFormat("%.2f", 100. * c.second.value);
unit = "%";
} else {
- s = HumanReadableNumber(c.second.value, c.second.oneK);
+ s = HumanReadableNumber(
+ c.second.value, c.second.oneK,
+ (c.second.flags & Counter::kStringifyExponent) != 0);
if ((c.second.flags & Counter::kIsRate) != 0) {
unit = (c.second.flags & Counter::kInvert) != 0 ? "s" : "/s";
}
diff --git a/src/string_util.cc b/src/string_util.cc
index 9a0d542..8e70de8 100644
--- a/src/string_util.cc
+++ b/src/string_util.cc
@@ -109,9 +109,16 @@
}
std::string ToBinaryStringFullySpecified(double value, int precision,
- Counter::OneK one_k) {
+ Counter::OneK one_k,
+ bool stringify_exponent = false) {
auto [mantissa, exponent] = ToExponentAndMantissa(
value, precision, one_k == Counter::kIs1024 ? 1024.0 : 1000.0);
+ if (stringify_exponent) {
+ const int64_t factor = (one_k == Counter::kIs1024 ? 10 : 3);
+ return StrFormat("%s%s%i", mantissa.c_str(),
+ (one_k == Counter::kIs1024 ? "*2^" : "e"),
+ static_cast<int>(exponent * factor));
+ }
return mantissa + ExponentToPrefix(exponent, one_k == Counter::kIs1024);
}
@@ -154,8 +161,9 @@
} // end namespace
-std::string HumanReadableNumber(double n, Counter::OneK one_k) {
- return ToBinaryStringFullySpecified(n, 1, one_k);
+std::string HumanReadableNumber(double n, Counter::OneK one_k,
+ bool stringify_exponent) {
+ return ToBinaryStringFullySpecified(n, 1, one_k, stringify_exponent);
}
std::string StrFormat(const char* format, ...) {
diff --git a/src/string_util.h b/src/string_util.h
index 1a84666..1c57fa2 100644
--- a/src/string_util.h
+++ b/src/string_util.h
@@ -13,7 +13,8 @@
namespace benchmark {
BENCHMARK_EXPORT
-std::string HumanReadableNumber(double n, Counter::OneK one_k);
+std::string HumanReadableNumber(double n, Counter::OneK one_k,
+ bool stringify_exponent = false);
BENCHMARK_EXPORT
std::string StrFormat(const char* format, ...) PRINTF_FORMAT_STRING_FUNC(1, 2);
diff --git a/test/string_util_gtest.cc b/test/string_util_gtest.cc
index 5a9a09e..12eb2f1 100644
--- a/test/string_util_gtest.cc
+++ b/test/string_util_gtest.cc
@@ -200,4 +200,63 @@
ASSERT_THAT(str, ::testing::MatchesRegex(std::get<2>(GetParam())));
}
+using HumanReadableStringifyExponentFixture = ::testing::TestWithParam<
+ std::tuple<double, benchmark::Counter::OneK, std::string>>;
+
+INSTANTIATE_TEST_SUITE_P(
+ HumanReadableStringifyExponentTests, HumanReadableStringifyExponentFixture,
+ ::testing::Values(
+ // Base 1024 positive powers
+ std::make_tuple(0.0, benchmark::Counter::kIs1024, "0*2^0"),
+ std::make_tuple(999.0, benchmark::Counter::kIs1024, "999*2^0"),
+ std::make_tuple(1000.0, benchmark::Counter::kIs1024, "1000*2^0"),
+ std::make_tuple(1024.0, benchmark::Counter::kIs1024, "1*2^10"),
+ std::make_tuple(1000 * 1000.0, benchmark::Counter::kIs1024,
+ "976.562*2^10"),
+ std::make_tuple(1024 * 1024.0, benchmark::Counter::kIs1024, "1*2^20"),
+ std::make_tuple(1000 * 1000 * 1000.0, benchmark::Counter::kIs1024,
+ "953.674*2^20"),
+ std::make_tuple(1024 * 1024 * 1024.0, benchmark::Counter::kIs1024,
+ "1*2^30"),
+ // Base 1024 negative powers
+ std::make_tuple(1.0 / 1024.0, benchmark::Counter::kIs1024, "1*2^-10"),
+ std::make_tuple(1.0 / (1024.0 * 1024.0), benchmark::Counter::kIs1024,
+ "1*2^-20"),
+ std::make_tuple(1.0 / (1024.0 * 1024.0 * 1024.0),
+ benchmark::Counter::kIs1024, "1*2^-30"),
+ std::make_tuple(1.0 / 1000.0, benchmark::Counter::kIs1024,
+ "1.024*2^-10"),
+ std::make_tuple(1.0 / (1000.0 * 1000.0), benchmark::Counter::kIs1024,
+ "1.04858*2^-20"),
+ std::make_tuple(1.0 / (1000.0 * 1000.0 * 1000.0),
+ benchmark::Counter::kIs1024, "1.07374*2^-30"),
+ // Base 1000 positive powers
+ std::make_tuple(0.0, benchmark::Counter::kIs1000, "0e0"),
+ std::make_tuple(999.0, benchmark::Counter::kIs1000, "999e0"),
+ std::make_tuple(1000.0, benchmark::Counter::kIs1000, "1e3"),
+ std::make_tuple(1024.0, benchmark::Counter::kIs1000, "1.024e3"),
+ std::make_tuple(1000 * 1000.0, benchmark::Counter::kIs1000, "1e6"),
+ std::make_tuple(1024 * 1024.0, benchmark::Counter::kIs1000,
+ "1.04858e6"),
+ std::make_tuple(1000 * 1000 * 1000.0, benchmark::Counter::kIs1000,
+ "1e9"),
+ std::make_tuple(1024 * 1024 * 1024.0, benchmark::Counter::kIs1000,
+ "1.07374e9"),
+ // Base 1000 negative powers
+ std::make_tuple(1e-3, benchmark::Counter::kIs1000, "1e-3"),
+ std::make_tuple(1e-6, benchmark::Counter::kIs1000, "1e-6"),
+ std::make_tuple(1e-9, benchmark::Counter::kIs1000, "1e-9"),
+ std::make_tuple(1.0 / 1024.0, benchmark::Counter::kIs1000,
+ "976.562e-6"),
+ std::make_tuple(1.0 / (1024.0 * 1024.0), benchmark::Counter::kIs1000,
+ "953.674e-9")));
+
+TEST_P(HumanReadableStringifyExponentFixture,
+ HumanReadableNumberStringifyExponent) {
+ std::string str = benchmark::HumanReadableNumber(std::get<0>(GetParam()),
+ std::get<1>(GetParam()),
+ /*stringify_exponent=*/true);
+ EXPECT_EQ(str, std::get<2>(GetParam()));
+}
+
} // end namespace
diff --git a/test/user_counters_test.cc b/test/user_counters_test.cc
index 9253d59..cdebd45 100644
--- a/test/user_counters_test.cc
+++ b/test/user_counters_test.cc
@@ -588,6 +588,58 @@
} // end namespace
// ========================================================================= //
+// ----------------- Stringify Exponent Counters Output -------------------- //
+// ========================================================================= //
+
+namespace {
+void BM_Counters_kStringifyExponent(benchmark::State& state) {
+ for (auto _ : state) {
+ // This test requires a non-zero CPU time to avoid divide-by-zero
+ auto iterations = static_cast<double>(state.iterations()) *
+ static_cast<double>(state.iterations());
+ benchmark::DoNotOptimize(iterations);
+ }
+ namespace bm = benchmark;
+ state.counters["foo"] =
+ bm::Counter{1.2345e-4, bm::Counter::kStringifyExponent};
+ state.counters["bar"] = bm::Counter{
+ 2.3456e4, bm::Counter::kStringifyExponent | bm::Counter::kIsRate};
+}
+BENCHMARK(BM_Counters_kStringifyExponent);
+ADD_CASES(TC_ConsoleOut,
+ {{"^BM_Counters_kStringifyExponent "
+ "%console_report bar=[0-9]+([.][0-9]+)?e[+-]?[0-9]+/s "
+ "foo=[0-9]+([.][0-9]+)?e[+-]?[0-9]+$"}});
+ADD_CASES(TC_JSONOut,
+ {{"\"name\": \"BM_Counters_kStringifyExponent\",$"},
+ {"\"family_index\": 12,$", MR_Next},
+ {"\"per_family_instance_index\": 0,$", MR_Next},
+ {"\"run_name\": \"BM_Counters_kStringifyExponent\",$", MR_Next},
+ {"\"run_type\": \"iteration\",$", MR_Next},
+ {"\"repetitions\": 1,$", MR_Next},
+ {"\"repetition_index\": 0,$", MR_Next},
+ {"\"threads\": 1,$", MR_Next},
+ {"\"iterations\": %int,$", MR_Next},
+ {"\"real_time\": %float,$", MR_Next},
+ {"\"cpu_time\": %float,$", MR_Next},
+ {"\"time_unit\": \"ns\",$", MR_Next},
+ {"\"bar\": %float,$", MR_Next},
+ {"\"foo\": %float$", MR_Next},
+ {"}", MR_Next}});
+ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kStringifyExponent\",%csv_report,"
+ "%float,%float$"}});
+// VS2013 does not allow this function to be passed as a lambda argument
+// to CHECK_BENCHMARK_RESULTS()
+void CheckStringifyExponent(Results const& e) {
+ double t = e.DurationCPUTime();
+ CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1.2345e-4, 0.001);
+ CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2.3456e4 / t, 0.001);
+}
+CHECK_BENCHMARK_RESULTS("BM_Counters_kStringifyExponent",
+ &CheckStringifyExponent);
+} // end namespace
+
+// ========================================================================= //
// --------------------------- TEST CASES END ------------------------------ //
// ========================================================================= //