From 53a83e855ec5c2a5659dd4ceacf15c00435eeaa6 Mon Sep 17 00:00:00 2001 From: mlimber Date: Fri, 26 Apr 2019 11:14:33 -0400 Subject: [PATCH 1/3] Add support for custom precision in floating point stringification Closes #1612 --- include/internal/catch_tostring.cpp | 25 +++++++++++++++++-- include/internal/catch_tostring.h | 9 +++++++ .../UsageTests/ToStringGeneral.tests.cpp | 23 +++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/include/internal/catch_tostring.cpp b/include/internal/catch_tostring.cpp index b857d3fb..33d442d6 100644 --- a/include/internal/catch_tostring.cpp +++ b/include/internal/catch_tostring.cpp @@ -234,11 +234,32 @@ std::string StringMaker::convert(std::nullptr_t) { return "nullptr"; } +int StringMaker::m_precision = 5; + std::string StringMaker::convert(float value) { - return fpToString(value, 5) + 'f'; + return fpToString(value, m_precision) + 'f'; } + +void StringMaker::setPrecision(int precision) { + m_precision = precision; +} + +int StringMaker::getPrecision() { + return m_precision; +} + +int StringMaker::m_precision = 10; + std::string StringMaker::convert(double value) { - return fpToString(value, 10); + return fpToString(value, m_precision); +} + +void StringMaker::setPrecision(int precision) { + m_precision = precision; +} + +int StringMaker::getPrecision() { + return m_precision; } std::string ratio_string::symbol() { return "a"; } diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 52634a8c..89d0d633 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -261,10 +261,19 @@ namespace Catch { template<> struct StringMaker { static std::string convert(float value); + static void setPrecision(int precision); + static int getPrecision(); + private: + static int m_precision; }; + template<> struct StringMaker { static std::string convert(double value); + static void setPrecision(int precision); + static int getPrecision(); + private: + static int m_precision; }; template diff --git a/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp b/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp index 09ac3045..bbcc9a66 100644 --- a/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp +++ b/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp @@ -128,6 +128,29 @@ TEST_CASE("String views are stringified like other strings", "[toString][approva #endif +//TEMPLATE_TEST_CASE("Floating-point precision can be set", "[toString][floatingPoint]", float, double) +//{ +// const auto oldPrecision = Catch::StringMaker::getPrecision(); +// const auto precision = GENERATE(-1, 0, 3, std::numeric_limits::max_digits10); +// const auto expectedLength = unsigned(precision < 0 ? 3 : precision); +// +// CAPTURE( precision ); +// +// if (precision >= 0) +// { +// Catch::StringMaker::setPrecision(precision); +// } +// +// // Expected to fail to demonstrate the problem +// const auto str = Catch::StringMaker::convert(std::numeric_limits::epsilon()); +// CHECK(str.length() >= expectedLength); +// +// if (precision >= 0) +// { +// Catch::StringMaker::setPrecision(oldPrecision); +// } +//} + namespace { struct WhatException : std::exception { From 1161011dd0c8b08ef26aabf99f1e28ab9604108a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Thu, 2 May 2019 21:32:54 +0200 Subject: [PATCH 2/3] Refactor custom precision in floating point stringification Also fixup tests. --- include/internal/catch_tostring.cpp | 24 ++------ include/internal/catch_tostring.h | 10 +--- .../Baselines/compact.sw.approved.txt | 4 ++ .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 38 ++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 4 +- .../SelfTest/Baselines/xml.sw.approved.txt | 45 ++++++++++++++- .../UsageTests/ToStringGeneral.tests.cpp | 55 +++++++++++-------- 8 files changed, 127 insertions(+), 57 deletions(-) diff --git a/include/internal/catch_tostring.cpp b/include/internal/catch_tostring.cpp index 33d442d6..f59676e7 100644 --- a/include/internal/catch_tostring.cpp +++ b/include/internal/catch_tostring.cpp @@ -234,32 +234,16 @@ std::string StringMaker::convert(std::nullptr_t) { return "nullptr"; } -int StringMaker::m_precision = 5; +int StringMaker::precision = 5; std::string StringMaker::convert(float value) { - return fpToString(value, m_precision) + 'f'; + return fpToString(value, precision) + 'f'; } -void StringMaker::setPrecision(int precision) { - m_precision = precision; -} - -int StringMaker::getPrecision() { - return m_precision; -} - -int StringMaker::m_precision = 10; +int StringMaker::precision = 10; std::string StringMaker::convert(double value) { - return fpToString(value, m_precision); -} - -void StringMaker::setPrecision(int precision) { - m_precision = precision; -} - -int StringMaker::getPrecision() { - return m_precision; + return fpToString(value, precision); } std::string ratio_string::symbol() { return "a"; } diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 89d0d633..cb248ea9 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -261,19 +261,13 @@ namespace Catch { template<> struct StringMaker { static std::string convert(float value); - static void setPrecision(int precision); - static int getPrecision(); - private: - static int m_precision; + static int precision; }; template<> struct StringMaker { static std::string convert(double value); - static void setPrecision(int precision); - static int getPrecision(); - private: - static int m_precision; + static int precision; }; template diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 259954e4..8447e839 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -859,6 +859,10 @@ Condition.tests.cpp:: passed: cpc != 0 for: 0x != 0 Condition.tests.cpp:: passed: returnsNull() == 0 for: {null string} == 0 Condition.tests.cpp:: passed: returnsConstNull() == 0 for: {null string} == 0 Condition.tests.cpp:: passed: 0 != p for: 0 != 0x +ToStringGeneral.tests.cpp:: passed: str1.size() == 3 + 5 for: 8 == 8 +ToStringGeneral.tests.cpp:: passed: str2.size() == 3 + 10 for: 13 == 13 +ToStringGeneral.tests.cpp:: passed: str1.size() == 2 + 5 for: 7 == 7 +ToStringGeneral.tests.cpp:: passed: str2.size() == 2 + 15 for: 17 == 17 Matchers.tests.cpp:: passed: "foo", Predicate([] (const char* const&) { return true; }) for: "foo" matches undescribed predicate CmdLine.tests.cpp:: passed: result for: {?} CmdLine.tests.cpp:: passed: config.processName == "" for: "" == "" diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 0a4de923..aa991bd4 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1299,6 +1299,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 266 | 199 passed | 63 failed | 4 failed as expected -assertions: 1449 | 1304 passed | 124 failed | 21 failed as expected +test cases: 267 | 200 passed | 63 failed | 4 failed as expected +assertions: 1453 | 1308 passed | 124 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 2b998afc..e13a6c23 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -6192,6 +6192,40 @@ Condition.tests.cpp:: PASSED: with expansion: 0 != 0x +------------------------------------------------------------------------------- +Precision of floating point stringification can be set + Floats +------------------------------------------------------------------------------- +ToStringGeneral.tests.cpp: +............................................................................... + +ToStringGeneral.tests.cpp:: PASSED: + CHECK( str1.size() == 3 + 5 ) +with expansion: + 8 == 8 + +ToStringGeneral.tests.cpp:: PASSED: + REQUIRE( str2.size() == 3 + 10 ) +with expansion: + 13 == 13 + +------------------------------------------------------------------------------- +Precision of floating point stringification can be set + Double +------------------------------------------------------------------------------- +ToStringGeneral.tests.cpp: +............................................................................... + +ToStringGeneral.tests.cpp:: PASSED: + CHECK( str1.size() == 2 + 5 ) +with expansion: + 7 == 7 + +ToStringGeneral.tests.cpp:: PASSED: + REQUIRE( str2.size() == 2 + 15 ) +with expansion: + 17 == 17 + ------------------------------------------------------------------------------- Predicate matcher can accept const char* ------------------------------------------------------------------------------- @@ -11389,6 +11423,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 266 | 183 passed | 79 failed | 4 failed as expected -assertions: 1466 | 1304 passed | 141 failed | 21 failed as expected +test cases: 267 | 184 passed | 79 failed | 4 failed as expected +assertions: 1470 | 1308 passed | 141 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index 7f4b43db..a7b77533 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -574,6 +574,8 @@ Message.tests.cpp: + + diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index fa078ac8..fcd6ff13 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -7778,6 +7778,47 @@ Nor would this + +
+ + + str1.size() == 3 + 5 + + + 8 == 8 + + + + + str2.size() == 3 + 10 + + + 13 == 13 + + + +
+
+ + + str1.size() == 2 + 5 + + + 7 == 7 + + + + + str2.size() == 2 + 15 + + + 17 == 17 + + + +
+ +
@@ -13728,7 +13769,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp b/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp index bbcc9a66..69d6320d 100644 --- a/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp +++ b/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp @@ -128,28 +128,39 @@ TEST_CASE("String views are stringified like other strings", "[toString][approva #endif -//TEMPLATE_TEST_CASE("Floating-point precision can be set", "[toString][floatingPoint]", float, double) -//{ -// const auto oldPrecision = Catch::StringMaker::getPrecision(); -// const auto precision = GENERATE(-1, 0, 3, std::numeric_limits::max_digits10); -// const auto expectedLength = unsigned(precision < 0 ? 3 : precision); -// -// CAPTURE( precision ); -// -// if (precision >= 0) -// { -// Catch::StringMaker::setPrecision(precision); -// } -// -// // Expected to fail to demonstrate the problem -// const auto str = Catch::StringMaker::convert(std::numeric_limits::epsilon()); -// CHECK(str.length() >= expectedLength); -// -// if (precision >= 0) -// { -// Catch::StringMaker::setPrecision(oldPrecision); -// } -//} +TEST_CASE("Precision of floating point stringification can be set", "[toString][floatingPoint]") { + SECTION("Floats") { + using sm = Catch::StringMaker; + const auto oldPrecision = sm::precision; + + const float testFloat = 1.12345678901234567899f; + auto str1 = sm::convert(testFloat); + sm::precision = 5; + // "1." prefix = 2 chars, f suffix is another char + CHECK(str1.size() == 3 + 5); + + sm::precision = 10; + auto str2 = sm::convert(testFloat); + REQUIRE(str2.size() == 3 + 10); + sm::precision = oldPrecision; + } + SECTION("Double") { + using sm = Catch::StringMaker; + const auto oldPrecision = sm::precision; + + const double testDouble = 1.123456789012345678901234567899; + sm::precision = 5; + auto str1 = sm::convert(testDouble); + // "1." prefix = 2 chars + CHECK(str1.size() == 2 + 5); + + sm::precision = 15; + auto str2 = sm::convert(testDouble); + REQUIRE(str2.size() == 2 + 15); + + sm::precision = oldPrecision; + } +} namespace { From e2862a8d7150da6afd13b6af58c79d4d06a024eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Fri, 3 May 2019 15:40:21 +0200 Subject: [PATCH 3/3] Add documentation for custom precision in float stringification --- docs/tostring.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/tostring.md b/docs/tostring.md index 77322dc6..549f8aed 100644 --- a/docs/tostring.md +++ b/docs/tostring.md @@ -7,6 +7,8 @@ [Catch::is_range specialisation](#catchis_range-specialisation)
[Exceptions](#exceptions)
[Enums](#enums)
+[Floating point precision](#floating-point-precision)
+ Catch needs to be able to convert types you use in assertions and logging expressions into strings (for logging and reporting purposes). Most built-in or std types are supported out of the box but there are two ways that you can tell Catch how to convert your own types (or other, third-party types) into strings. @@ -104,6 +106,22 @@ TEST_CASE() { } ``` +## Floating point precision + +Catch provides a built-in `StringMaker` specialization for both `float` +`double`. By default, it uses what we think is a reasonable precision, +but you can customize it by modifying the `precision` static variable +inside the `StringMaker` specialization, like so: + +```cpp + Catch::StringMaker::precision = 15; + const float testFloat1 = 1.12345678901234567899f; + const float testFloat2 = 1.12345678991234567899f; + REQUIRE(testFloat1 == testFloat2); +``` + +This assertion will fail and print out the `testFloat1` and `testFloat2` +to 15 decimal places. ---