Skip to content

Commit 6cbf0f5

Browse files
committed
extended truncation tests to highlight overflow issue
1 parent b6df881 commit 6cbf0f5

File tree

2 files changed

+55
-14
lines changed

2 files changed

+55
-14
lines changed

test/runtime/almost_equals.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase {
4949
const auto maxXYOne = std::max({rep{1}, abs(x), abs(y)});
5050
return abs(x - y) <= std::numeric_limits<rep>::epsilon() * maxXYOne;
5151
} else {
52-
if (x >= 0) {
52+
if (x > 0) {
5353
return x - 1 <= y && y - 1 <= x;
5454
} else {
5555
return x <= y + 1 && y <= x + 1;

test/runtime/truncation_test.cpp

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ inline constexpr struct half_revolution : named_unit<"hrev", mag_pi * radian> {
4141
} half_revolution;
4242
inline constexpr auto hrev = half_revolution;
4343

44-
// constexpr auto revb6 = mag_ratio<1,3> * mag_pi * rad;
45-
4644
TEST_CASE("value_cast should not truncate for valid inputs", "[value_cast]")
4745
{
4846
SECTION("num > den > 1, irr = 1")
@@ -92,8 +90,18 @@ TEST_CASE("value_cast should not truncate for valid inputs", "[value_cast]")
9290
}
9391

9492

93+
inline constexpr std::int64_t one_64bit = 1;
94+
inline constexpr struct one_in_2to50 : named_unit<"oi2t50", mag_ratio<(one_64bit << 50) + 1, (one_64bit << 50)> * one> {
95+
} one_in_2to50;
96+
inline constexpr auto oi2t50 = one_in_2to50;
97+
98+
inline constexpr struct one_in_2to60 : named_unit<"oi2t60", mag_ratio<(one_64bit << 60) + 1, (one_64bit << 60)> * one> {
99+
} one_in_2to60;
100+
inline constexpr auto oi2t60 = one_in_2to60;
101+
102+
95103
TEMPLATE_TEST_CASE("value_cast should not overflow internally for valid inputs", "[value_cast]", std::int8_t,
96-
std::uint8_t, std::int16_t, std::uint16_t, std::int32_t, std::uint32_t)
104+
std::uint8_t, std::int16_t, std::uint16_t, std::int32_t, std::uint32_t, std::int64_t, std::uint64_t)
97105
{
98106
// max()/20: small enough so that none of the tested factors are likely to cause overflow, but still be nonzero;
99107
// the "easy" test to verify the test itself is good.
@@ -103,24 +111,57 @@ TEMPLATE_TEST_CASE("value_cast should not overflow internally for valid inputs",
103111
test_values.push_back(std::numeric_limits<TestType>::min() + 1);
104112
}
105113

106-
for (TestType tv : test_values) {
107-
SECTION("grad <-> deg")
108-
{
109-
auto deg_number = static_cast<TestType>(std::trunc(0.9 * tv));
110-
auto grad_number = static_cast<TestType>(std::round(deg_number / 0.9));
114+
SECTION("grad <-> deg")
115+
{
116+
for (TestType tv : test_values) {
117+
// non-overflowing computation for b = 360/400 * a = (10 - 1)/10 * a = (1 - 1/10) * a = 1 - a/10
118+
auto deg_number = tv - tv / 10;
119+
// non-overflowing computation for b = 400/360 * a = (9 + 1)/9 * a = (1 + 1/9) * a = 1 + a/9
120+
auto grad_number = deg_number + deg_number / 9;
111121
INFO(MP_UNITS_STD_FMT::format("{} deg ~ {} grad", deg_number, grad_number));
112122
REQUIRE_THAT(value_cast<grad>(deg_number * deg), AlmostEquals(grad_number * grad));
113123
REQUIRE_THAT(value_cast<deg>(grad_number * grad), AlmostEquals(deg_number * deg));
114124
}
125+
}
126+
127+
if constexpr (std::numeric_limits<TestType>::digits >= 60) {
128+
// ---- a couple of pathological conversion factors
115129

130+
// this one can still be correctly calculated using a double-precision calculation
131+
SECTION("one <-> (1 + 2^-50) * one")
132+
{
133+
for (TestType tv : test_values) {
134+
auto n1 = tv - tv >> 50;
135+
auto n2 = n1 + n1 / ((one_64bit << 50) - 1);
136+
INFO(MP_UNITS_STD_FMT::format("{} (1 + 2^-50) * one ~ {} one", n1, n2));
137+
REQUIRE_THAT(value_cast<one>(n1 * oi2t50), AlmostEquals(n2 * one));
138+
REQUIRE_THAT(value_cast<oi2t50>(n2 * one), AlmostEquals(n1 * oi2t50));
139+
}
140+
}
116141

142+
// this one cannot be correctly calculated in double-precision
143+
SECTION("one <-> (1 + 2^-60) * one")
144+
{
145+
for (TestType tv : test_values) {
146+
auto n1 = tv - tv >> 60;
147+
auto n2 = n1 + n1 / ((one_64bit << 60) - 1);
148+
INFO(MP_UNITS_STD_FMT::format("{} (1 + 2^-60) * one ~ {} one", n1, n2));
149+
REQUIRE_THAT(value_cast<one>(n1 * oi2t60), AlmostEquals(n2 * one));
150+
REQUIRE_THAT(value_cast<oi2t60>(n2 * one), AlmostEquals(n1 * oi2t60));
151+
}
152+
}
153+
} else {
154+
// skipping this oen for the 64 bit types; we don't know how to calculate the expected results to 64 bits
155+
// precision...
117156
SECTION("rad <-> rev")
118157
{
119-
auto rev_number = static_cast<TestType>(std::trunc(0.5 * std::numbers::inv_pi * tv));
120-
auto rad_number = static_cast<TestType>(std::round(2 * std::numbers::pi * rev_number));
121-
INFO(MP_UNITS_STD_FMT::format("{} rev ~ {} rad", rev_number, rad_number));
122-
REQUIRE_THAT(value_cast<rad>(rev_number * rev), AlmostEquals(rad_number * rad));
123-
REQUIRE_THAT(value_cast<rev>(rad_number * rad), AlmostEquals(rev_number * rev));
158+
for (TestType tv : test_values) {
159+
auto rev_number = static_cast<TestType>(std::trunc(0.5 * std::numbers::inv_pi * tv));
160+
auto rad_number = static_cast<TestType>(std::round(2 * std::numbers::pi * rev_number));
161+
INFO(MP_UNITS_STD_FMT::format("{} rev ~ {} rad", rev_number, rad_number));
162+
REQUIRE_THAT(value_cast<rad>(rev_number * rev), AlmostEquals(rad_number * rad));
163+
REQUIRE_THAT(value_cast<rev>(rad_number * rad), AlmostEquals(rev_number * rev));
164+
}
124165
}
125166
}
126167
}

0 commit comments

Comments
 (0)