From bf0ddfc57b1badcdf48b826d0e24044c9824aaa7 Mon Sep 17 00:00:00 2001 From: Gregor Jasny Date: Sun, 10 Jan 2021 00:31:14 +0100 Subject: [PATCH] fix: use printf %g or std::to_chars to serialize doubles This will result in floating point numbers redered with the necessary precision and avoids trailing zeroes. Closes: #441 --- core/src/text_serializer.cc | 36 ++++++++++++++++++++++-------- core/tests/serializer_test.cc | 2 +- core/tests/text_serializer_test.cc | 24 +++++++++----------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/core/src/text_serializer.cc b/core/src/text_serializer.cc index c0267941..d73964b2 100644 --- a/core/src/text_serializer.cc +++ b/core/src/text_serializer.cc @@ -1,10 +1,17 @@ #include "prometheus/text_serializer.h" +#include #include -#include -#include #include #include +#include + +#if __cpp_lib_to_chars >= 201611L +#include +#else +#include +#include +#endif namespace prometheus { @@ -17,14 +24,25 @@ void WriteValue(std::ostream& out, double value) { } else if (std::isinf(value)) { out << (value < 0 ? "-Inf" : "+Inf"); } else { - std::ios oldState{nullptr}; - oldState.copyfmt(out); + std::array buffer; - out.setf(std::ios::fixed, std::ios::floatfield); - out << std::setprecision(std::numeric_limits::max_digits10); - out << value; - - out.copyfmt(oldState); +#if __cpp_lib_to_chars >= 201611L + auto [ptr, ec] = + std::to_chars(buffer.data(), buffer.data() + buffer.size(), value); + if (ec != std::errc()) { + throw std::runtime_error("Could not convert double to string: " + ec); + } + out.write(buffer.data(), ptr - buffer.data()); +#else + auto wouldHaveWritten = + std::snprintf(buffer.data(), buffer.size(), "%.*g", + std::numeric_limits::max_digits10 - 1, value); + if (wouldHaveWritten <= 0 || + static_cast(wouldHaveWritten) >= buffer.size()) { + throw std::runtime_error("Could not convert double to string"); + } + out.write(buffer.data(), wouldHaveWritten); +#endif } } diff --git a/core/tests/serializer_test.cc b/core/tests/serializer_test.cc index 7e9e26a3..528f649d 100644 --- a/core/tests/serializer_test.cc +++ b/core/tests/serializer_test.cc @@ -43,7 +43,7 @@ TEST_F(SerializerTest, shouldSerializeLocaleIndependent) { } const auto serialized = textSerializer.Serialize(collected); - EXPECT_THAT(serialized, testing::HasSubstr("1.0")); + EXPECT_THAT(serialized, testing::HasSubstr(" 1\n")); } #endif diff --git a/core/tests/text_serializer_test.cc b/core/tests/text_serializer_test.cc index bacd5345..6b9bc479 100644 --- a/core/tests/text_serializer_test.cc +++ b/core/tests/text_serializer_test.cc @@ -69,7 +69,7 @@ TEST_F(TextSerializerTest, shouldSerializeUntyped) { metric.untyped.value = 64.0; const auto serialized = Serialize(MetricType::Untyped); - EXPECT_THAT(serialized, testing::HasSubstr(name + " 64.00000000000000000")); + EXPECT_THAT(serialized, testing::HasSubstr(name + " 64\n")); } TEST_F(TextSerializerTest, shouldSerializeTimestamp) { @@ -77,8 +77,7 @@ TEST_F(TextSerializerTest, shouldSerializeTimestamp) { metric.timestamp_ms = 1234; const auto serialized = Serialize(MetricType::Counter); - EXPECT_THAT(serialized, - testing::HasSubstr(name + " 64.00000000000000000 1234")); + EXPECT_THAT(serialized, testing::HasSubstr(name + " 64 1234\n")); } TEST_F(TextSerializerTest, shouldSerializeHistogramWithNoBuckets) { @@ -87,7 +86,7 @@ TEST_F(TextSerializerTest, shouldSerializeHistogramWithNoBuckets) { const auto serialized = Serialize(MetricType::Histogram); EXPECT_THAT(serialized, testing::HasSubstr(name + "_count 2")); - EXPECT_THAT(serialized, testing::HasSubstr(name + "_sum 32.000000000000000")); + EXPECT_THAT(serialized, testing::HasSubstr(name + "_sum 32\n")); EXPECT_THAT(serialized, testing::HasSubstr(name + "_bucket{le=\"+Inf\"} 2")); } @@ -98,11 +97,11 @@ TEST_F(TextSerializerTest, shouldSerializeHistogram) { metric = histogram.Collect(); const auto serialized = Serialize(MetricType::Histogram); - EXPECT_THAT(serialized, testing::HasSubstr(name + "_count 2")); - EXPECT_THAT(serialized, testing::HasSubstr(name + "_sum 200.00000000000000")); - EXPECT_THAT(serialized, testing::HasSubstr( - name + "_bucket{le=\"1.00000000000000000\"} 1")); - EXPECT_THAT(serialized, testing::HasSubstr(name + "_bucket{le=\"+Inf\"} 2")); + EXPECT_THAT(serialized, testing::HasSubstr(name + "_count 2\n")); + EXPECT_THAT(serialized, testing::HasSubstr(name + "_sum 200\n")); + EXPECT_THAT(serialized, testing::HasSubstr(name + "_bucket{le=\"1\"} 1\n")); + EXPECT_THAT(serialized, + testing::HasSubstr(name + "_bucket{le=\"+Inf\"} 2\n")); } TEST_F(TextSerializerTest, shouldSerializeSummary) { @@ -113,11 +112,8 @@ TEST_F(TextSerializerTest, shouldSerializeSummary) { const auto serialized = Serialize(MetricType::Summary); EXPECT_THAT(serialized, testing::HasSubstr(name + "_count 2")); - EXPECT_THAT(serialized, testing::HasSubstr(name + "_sum 200.00000000000000")); - EXPECT_THAT( - serialized, - testing::HasSubstr( - name + "{quantile=\"0.50000000000000000\"} 0.0000000000000000")); + EXPECT_THAT(serialized, testing::HasSubstr(name + "_sum 200\n")); + EXPECT_THAT(serialized, testing::HasSubstr(name + "{quantile=\"0.5\"} 0\n")); } } // namespace