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

Fuzz-like testers for developing explicit-rep checkers #346

Closed
wants to merge 9 commits into from
Closed
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
17 changes: 17 additions & 0 deletions au/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ cc_library(
":fwd",
":operators",
":rep",
":static_cast_checkers",
":unit_of_measure",
":utility",
":zero",
Expand Down Expand Up @@ -545,6 +546,22 @@ cc_test(
],
)

cc_library(
name = "static_cast_checkers",
hdrs = ["code/au/static_cast_checkers.hh"],
includes = ["code"],
)

cc_test(
name = "static_cast_checkers_test",
size = "small",
srcs = ["code/au/static_cast_checkers_test.cc"],
deps = [
":static_cast_checkers",
"@com_google_googletest//:gtest_main",
],
)

cc_library(
name = "stdx",
srcs = glob([
Expand Down
30 changes: 30 additions & 0 deletions au/code/au/apply_magnitude_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,36 @@ TEST(ApplyMagnitude, MultipliesSingleNumberForIrrationalMagnitudeOnFloatingPoint
EXPECT_THAT(apply_magnitude(2.0f, PI), SameTypeAndValue(2.0f * static_cast<float>(M_PI)));
}

TEST(WouldOverflow, AlwaysFalseForConversionFactorOfOne) {
{
using ApplyOneToI32 = ApplyMagnitudeT<int32_t, Magnitude<>>;

EXPECT_FALSE(ApplyOneToI32::would_overflow(2'147'483'647));
EXPECT_FALSE(ApplyOneToI32::would_overflow(1));
EXPECT_FALSE(ApplyOneToI32::would_overflow(0));
EXPECT_FALSE(ApplyOneToI32::would_overflow(-1));
EXPECT_FALSE(ApplyOneToI32::would_overflow(-2'147'483'648));
}

{
using ApplyOneToU8 = ApplyMagnitudeT<uint8_t, Magnitude<>>;

EXPECT_FALSE(ApplyOneToU8::would_overflow(255));
EXPECT_FALSE(ApplyOneToU8::would_overflow(1));
EXPECT_FALSE(ApplyOneToU8::would_overflow(0));
}

{
using ApplyOneToF = ApplyMagnitudeT<float, Magnitude<>>;

EXPECT_FALSE(ApplyOneToF::would_overflow(3.402e38f));
EXPECT_FALSE(ApplyOneToF::would_overflow(1.0f));
EXPECT_FALSE(ApplyOneToF::would_overflow(0.0f));
EXPECT_FALSE(ApplyOneToF::would_overflow(-1.0f));
EXPECT_FALSE(ApplyOneToF::would_overflow(-3.402e38f));
}
}

TEST(WouldOverflow, HasCorrectBoundariesForIntegerMultiply) {
auto ONE_BILLION = pow<9>(mag<10>());

Expand Down
75 changes: 75 additions & 0 deletions au/code/au/quantity.hh
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
#include "au/fwd.hh"
#include "au/operators.hh"
#include "au/rep.hh"
#include "au/static_cast_checkers.hh"
#include "au/stdx/functional.hh"
#include "au/unit_of_measure.hh"
#include "au/utility/type_traits.hh"
#include "au/zero.hh"

namespace au {

struct Inches;

//
// Make a Quantity of the given Unit, which has this value as measured in the Unit.
//
Expand Down Expand Up @@ -646,19 +649,91 @@ constexpr bool will_conversion_overflow(Quantity<U, R> q, TargetUnitSlot target_
q.in(U{}));
}

template <typename... Ts>
struct Typelist {};

template <typename L1, typename L2>
struct BlowUpIfMatch {
static void go() {}
};
template <typename... Ts, typename... Us>
struct BlowUpIfMatch<Typelist<Ts...>, Typelist<Us...>> {
static void go() {
if (stdx::conjunction<std::is_same<Ts, Us>...>::value) {
std::terminate();
}
}
};

// Check conversion for overflow (new rep).
template <typename TargetRep, typename U, typename R, typename TargetUnitSlot>
constexpr bool will_conversion_overflow(Quantity<U, R> q, TargetUnitSlot target_unit) {
using BUI = BlowUpIfMatch<Typelist<U, R, TargetRep, TargetUnitSlot>,
Typelist<Inches, uint8_t, int8_t, Inches>>;
// Someday, we would like a more efficient implementation --- one that simply computes, at
// compile time, the smallest value that would overflow, and then compares against that.
//
// This will at least let us get off the ground for now.
using Common = std::common_type_t<R, TargetRep>;
if (detail::will_static_cast_overflow<Common>(q.in(U{}))) {
// BUI::go();
return true;
}

const auto to_common = rep_cast<Common>(q);
if (will_conversion_overflow(to_common, target_unit)) {
// BUI::go();
return true;
}

const auto converted_but_not_narrowed = to_common.coerce_in(target_unit);
// return detail::will_static_cast_overflow<TargetRep>(converted_but_not_narrowed);
// ^
// ^ use this instead
const auto asdf = detail::will_static_cast_overflow<TargetRep>(converted_but_not_narrowed);
if (asdf) {
// BUI::go();
}
return asdf;
}

// Check conversion for truncation (no change of rep).
template <typename U, typename R, typename TargetUnitSlot>
constexpr bool will_conversion_truncate(Quantity<U, R> q, TargetUnitSlot target_unit) {
return detail::ApplyMagnitudeT<R, decltype(unit_ratio(U{}, target_unit))>::would_truncate(
q.in(U{}));
}

// Check conversion for truncation (new rep).
template <typename TargetRep, typename U, typename R, typename TargetUnitSlot>
constexpr bool will_conversion_truncate(Quantity<U, R> q, TargetUnitSlot target_unit) {
using Common = std::common_type_t<R, TargetRep>;
if (detail::will_static_cast_truncate<Common>(q.in(U{}))) {
return true;
}

const auto to_common = rep_cast<Common>(q);
if (will_conversion_truncate(to_common, target_unit)) {
return true;
}

const auto converted_but_not_narrowed = to_common.coerce_in(target_unit);
return detail::will_static_cast_truncate<TargetRep>(converted_but_not_narrowed);
}

// Check for any lossiness in conversion (no change of rep).
template <typename U, typename R, typename TargetUnitSlot>
constexpr bool is_conversion_lossy(Quantity<U, R> q, TargetUnitSlot target_unit) {
return will_conversion_truncate(q, target_unit) || will_conversion_overflow(q, target_unit);
}

// Check for any lossiness in conversion (new rep).
template <typename TargetRep, typename U, typename R, typename TargetUnitSlot>
constexpr bool is_conversion_lossy(Quantity<U, R> q, TargetUnitSlot target_unit) {
return will_conversion_truncate<TargetRep>(q, target_unit) ||
will_conversion_overflow<TargetRep>(q, target_unit);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Comparing and/or combining Quantities of different types.

Expand Down
18 changes: 18 additions & 0 deletions au/code/au/quantity_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,14 @@ TEST(WillConversionOverflow, SensitiveToTypeBoundariesForPureIntegerMultiply) {
}
}

TEST(WillConversionOverflow, AlwaysFalseForQuantityEquivalentUnits) {
auto will_m_to_m_overflow = [](auto x) { return will_conversion_overflow(meters(x), meters); };

EXPECT_FALSE(will_m_to_m_overflow(2'147'483));
EXPECT_FALSE(will_m_to_m_overflow(-2'147'483));
EXPECT_FALSE(will_m_to_m_overflow(uint8_t{255}));
}

TEST(WillConversionTruncate, UsesModForIntegerTypes) {
auto will_in_to_ft_truncate_i32 = [](int32_t x) {
return will_conversion_truncate(inches(x), feet);
Expand All @@ -878,6 +886,16 @@ TEST(WillConversionTruncate, UsesModForIntegerTypes) {
EXPECT_TRUE(will_in_to_ft_truncate_i32(-121));
}

TEST(WillConversionTruncate, AlwaysFalseForQuantityEquivalentUnits) {
auto will_in_to_in_truncate = [](auto x) {
return will_conversion_truncate(inches(x), inches);
};

EXPECT_FALSE(will_in_to_in_truncate(uint8_t{124}));
EXPECT_FALSE(will_in_to_in_truncate(0));
EXPECT_FALSE(will_in_to_in_truncate(-120));
}

TEST(IsConversionLossy, CorrectlyDiscriminatesBetweenLossyAndLosslessConversions) {
// We will check literally every representable value in the type, and make sure that the result
// of `is_conversion_lossy()` matches perfectly with the inability to recover the initial value.
Expand Down
Loading
Loading