Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom precision in error reports for floating-point numbers #1614

Merged
merged 3 commits into from
May 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/tostring.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
[Catch::is_range specialisation](#catchis_range-specialisation)<br>
[Exceptions](#exceptions)<br>
[Enums](#enums)<br>
[Floating point precision](#floating-point-precision)<br>


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.
Expand Down Expand Up @@ -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<float>::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.

---

Expand Down
9 changes: 7 additions & 2 deletions include/internal/catch_tostring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,16 @@ std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) {
return "nullptr";
}

int StringMaker<float>::precision = 5;

std::string StringMaker<float>::convert(float value) {
return fpToString(value, 5) + 'f';
return fpToString(value, precision) + 'f';
}

int StringMaker<double>::precision = 10;

std::string StringMaker<double>::convert(double value) {
return fpToString(value, 10);
return fpToString(value, precision);
}

std::string ratio_string<std::atto>::symbol() { return "a"; }
Expand Down
3 changes: 3 additions & 0 deletions include/internal/catch_tostring.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,13 @@ namespace Catch {
template<>
struct StringMaker<float> {
static std::string convert(float value);
static int precision;
};

template<>
struct StringMaker<double> {
static std::string convert(double value);
static int precision;
};

template <typename T>
Expand Down
4 changes: 4 additions & 0 deletions projects/SelfTest/Baselines/compact.sw.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,10 @@ Condition.tests.cpp:<line number>: passed: cpc != 0 for: 0x<hex digits> != 0
Condition.tests.cpp:<line number>: passed: returnsNull() == 0 for: {null string} == 0
Condition.tests.cpp:<line number>: passed: returnsConstNull() == 0 for: {null string} == 0
Condition.tests.cpp:<line number>: passed: 0 != p for: 0 != 0x<hex digits>
ToStringGeneral.tests.cpp:<line number>: passed: str1.size() == 3 + 5 for: 8 == 8
ToStringGeneral.tests.cpp:<line number>: passed: str2.size() == 3 + 10 for: 13 == 13
ToStringGeneral.tests.cpp:<line number>: passed: str1.size() == 2 + 5 for: 7 == 7
ToStringGeneral.tests.cpp:<line number>: passed: str2.size() == 2 + 15 for: 17 == 17
Matchers.tests.cpp:<line number>: passed: "foo", Predicate<const char*>([] (const char* const&) { return true; }) for: "foo" matches undescribed predicate
CmdLine.tests.cpp:<line number>: passed: result for: {?}
CmdLine.tests.cpp:<line number>: passed: config.processName == "" for: "" == ""
Expand Down
4 changes: 2 additions & 2 deletions projects/SelfTest/Baselines/console.std.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

38 changes: 36 additions & 2 deletions projects/SelfTest/Baselines/console.sw.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6192,6 +6192,40 @@ Condition.tests.cpp:<line number>: PASSED:
with expansion:
0 != 0x<hex digits>

-------------------------------------------------------------------------------
Precision of floating point stringification can be set
Floats
-------------------------------------------------------------------------------
ToStringGeneral.tests.cpp:<line number>
...............................................................................

ToStringGeneral.tests.cpp:<line number>: PASSED:
CHECK( str1.size() == 3 + 5 )
with expansion:
8 == 8

ToStringGeneral.tests.cpp:<line number>: PASSED:
REQUIRE( str2.size() == 3 + 10 )
with expansion:
13 == 13

-------------------------------------------------------------------------------
Precision of floating point stringification can be set
Double
-------------------------------------------------------------------------------
ToStringGeneral.tests.cpp:<line number>
...............................................................................

ToStringGeneral.tests.cpp:<line number>: PASSED:
CHECK( str1.size() == 2 + 5 )
with expansion:
7 == 7

ToStringGeneral.tests.cpp:<line number>: PASSED:
REQUIRE( str2.size() == 2 + 15 )
with expansion:
17 == 17

-------------------------------------------------------------------------------
Predicate matcher can accept const char*
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -11389,6 +11423,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: 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

4 changes: 3 additions & 1 deletion projects/SelfTest/Baselines/junit.sw.approved.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact
>
<testsuite name="<exe-name>" errors="17" failures="125" tests="1467" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="125" tests="1471" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="filters" value="~[!nonportable]~[!benchmark]~[approvals]"/>
<property name="random-seed" value="1"/>
Expand Down Expand Up @@ -574,6 +574,8 @@ Message.tests.cpp:<line number>
<testcase classname="<exe-name>.global" name="Parse test names and tags/empty quoted name" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Parse test names and tags/quoted string followed by tag exclusion" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Pointers can be compared to null" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Precision of floating point stringification can be set/Floats" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Precision of floating point stringification can be set/Double" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Predicate matcher can accept const char*" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Process can be configured on command line/empty args don't cause a crash" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Process can be configured on command line/default - no arguments" time="{duration}"/>
Expand Down
45 changes: 43 additions & 2 deletions projects/SelfTest/Baselines/xml.sw.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7778,6 +7778,47 @@ Nor would this
</Expression>
<OverallResult success="true"/>
</TestCase>
<TestCase name="Precision of floating point stringification can be set" tags="[floatingPoint][toString]" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Section name="Floats" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Expression success="true" type="CHECK" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str1.size() == 3 + 5
</Original>
<Expanded>
8 == 8
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str2.size() == 3 + 10
</Original>
<Expanded>
13 == 13
</Expanded>
</Expression>
<OverallResults successes="2" failures="0" expectedFailures="0"/>
</Section>
<Section name="Double" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Expression success="true" type="CHECK" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str1.size() == 2 + 5
</Original>
<Expanded>
7 == 7
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str2.size() == 2 + 15
</Original>
<Expanded>
17 == 17
</Expanded>
</Expression>
<OverallResults successes="2" failures="0" expectedFailures="0"/>
</Section>
<OverallResult success="true"/>
</TestCase>
<TestCase name="Predicate matcher can accept const char*" tags="[compilation][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
Expand Down Expand Up @@ -13728,7 +13769,7 @@ loose text artifact
</Section>
<OverallResult success="true"/>
</TestCase>
<OverallResults successes="1304" failures="142" expectedFailures="21"/>
<OverallResults successes="1308" failures="142" expectedFailures="21"/>
</Group>
<OverallResults successes="1304" failures="141" expectedFailures="21"/>
<OverallResults successes="1308" failures="141" expectedFailures="21"/>
</Catch>
34 changes: 34 additions & 0 deletions projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,40 @@ TEST_CASE("String views are stringified like other strings", "[toString][approva

#endif

TEST_CASE("Precision of floating point stringification can be set", "[toString][floatingPoint]") {
SECTION("Floats") {
using sm = Catch::StringMaker<float>;
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<double>;
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 {

struct WhatException : std::exception {
Expand Down