Skip to content
Open
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
2 changes: 1 addition & 1 deletion include/rapidcheck/gen/Numeric.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace rc {
namespace gen {

/// Generates an integer in a given range.
/// Generates an integer or floating point value in a given range.
///
/// @param min The minimum value, inclusive.
/// @param max The maximum value, exclusive.
Expand Down
71 changes: 60 additions & 11 deletions include/rapidcheck/gen/Numeric.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "rapidcheck/gen/Transform.h"
#include "rapidcheck/gen/detail/ScaleInteger.h"

#include <cmath>

namespace rc {
namespace gen {
namespace detail {
Expand Down Expand Up @@ -85,22 +87,25 @@ struct DefaultArbitrary<bool> {
static Gen<bool> arbitrary() { return boolean; }
};

} // namespace detail
template <typename T>
void assertValidRange(T min, T max) {
if (max <= min) {
std::string msg;
msg += "Invalid range [" + std::to_string(min);
msg += ", " + std::to_string(max) + ")";
throw GenerationFailure(msg);
}
}

template <typename T>
Gen<T> inRange(T min, T max) {
Gen<T> inRange(T min, T max, std::false_type) {
return [=](const Random &random, int size) {
if (max <= min) {
std::string msg;
msg += "Invalid range [" + std::to_string(min);
msg += ", " + std::to_string(max) + ")";
throw GenerationFailure(msg);
}
assertValidRange(min, max);

const auto rangeSize =
detail::scaleInteger(static_cast<Random::Number>(max) -
static_cast<Random::Number>(min) - 1,
size) +
scaleInteger(static_cast<Random::Number>(max) -
static_cast<Random::Number>(min) - 1,
size) +
1;
const auto value =
static_cast<T>((Random(random).next() % rangeSize) + min);
Expand All @@ -110,5 +115,49 @@ Gen<T> inRange(T min, T max) {
};
}

template <typename T>
void assertFiniteRange(T min, T max) {
if (!std::isfinite(min) || !std::isfinite(max)) {
std::string msg;
msg += "Non-finite range [" + std::to_string(min);
msg += ", " + std::to_string(max) + ")";
throw GenerationFailure(msg);
}
}

template <typename T>
Gen<T> inRange(T min, T max, std::true_type) {
return [=](const Random &random, int size) {
assertValidRange(min, max);
assertFiniteRange(min, max);

const auto unit =
static_cast<T>(Random(random).next()) /
(static_cast<T>(std::numeric_limits<Random::Number>::max()) + 1);
// As for integrals, `size` decides how much of the range is used, with a
// size of zero yielding only `min`.
const auto scale =
std::min(size, kNominalSize) / static_cast<T>(kNominalSize);

// Interpolate the value and limit it to the range [min, max)
const auto t = unit * scale;
auto value = ((1 - t) * min) + (t * max);
if (value >= max) {
value = std::nextafter(max, min);
}

assert(value >= min && value < max);
return shrinkable::shrinkRecur(
value, [=](T x) { return shrink::towards<T>(x, min); });
};
}

} // namespace detail

template <typename T>
Gen<T> inRange(T min, T max) {
return detail::inRange<T>(min, max, std::is_floating_point<T>());
}

} // namespace gen
} // namespace rc
5 changes: 3 additions & 2 deletions include/rapidcheck/shrink/Shrink.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ Seq<Container> removeChunks(Container elements);
template <typename Container, typename Shrink>
Seq<Container> eachElement(Container elements, Shrink shrink);

/// Shrinks an integral value towards another integral value.
/// Shrinks an integral or floating point value towards another value of the
/// same type by repeated bisection. The target is always tried first.
///
/// @param value The value to shrink.
/// @param target The integer to shrink towards.
/// @param target The value to shrink towards.
template <typename T>
Seq<T> towards(T value, T target);

Expand Down
32 changes: 31 additions & 1 deletion include/rapidcheck/shrink/Shrink.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <algorithm>
#include <cmath>
#include <locale>

Expand All @@ -11,7 +12,7 @@ namespace rc {
namespace shrink {
namespace detail {

template <typename T>
template <typename T, typename Enable = void>
class TowardsSeq {
public:
using UInt = typename std::make_unsigned<T>::type;
Expand All @@ -37,6 +38,35 @@ class TowardsSeq {
bool m_down;
};

template <typename T>
class TowardsSeq<
T,
typename std::enable_if<std::is_floating_point<T>::value>::type> {
public:
TowardsSeq(T value, T target)
: m_value(value)
, m_target(target) {}

Maybe<T> operator()() {
if (m_value == m_target || std::isnan(m_value) || std::isnan(m_target)) {
return Nothing;
}

T new_value = (m_value / 2) + (m_target / 2);

if (new_value == m_value || std::isnan(new_value)) {
new_value = m_target;
}

m_value = new_value;
return m_value;
}

private:
T m_value;
T m_target;
};

template <typename Container>
class RemoveChunksSeq {
public:
Expand Down
89 changes: 74 additions & 15 deletions test/gen/NumericTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,21 @@ TEST_CASE("arbitrary reals") {

namespace {

template <typename T>
Gen<std::pair<T, T>> genRangeOf() {
// TODO proper range generator
return gen::exec([] {
const auto a = *gen::arbitrary<T>();
const auto b = *gen::distinctFrom(a);
return std::make_pair(std::min(a, b), std::max(a, b));
});
}

struct InRangeProperties {
template <typename T>
static void exec() {

// TODO proper range generator
static const auto genRange = gen::exec([] {
const auto a = *gen::arbitrary<T>();
const auto b = *gen::distinctFrom(a);
return std::make_pair(std::min(a, b), std::max(a, b));
});
static const auto genRange = genRangeOf<T>();

templatedProp<T>(
"never generates values outside of range",
Expand Down Expand Up @@ -211,6 +216,22 @@ struct InRangeProperties {
GenerationFailure);
});

templatedProp<T>("when size == 0, generates only min",
[](const Random &random) {
const auto range = *genRange;
RC_ASSERT(
gen::inRange(range.first, range.second)(random, 0) ==
shrinkable::just(range.first));
});
}
};

struct InRangeIntegralProperties {
template <typename T>
static void exec() {

static const auto genRange = genRangeOf<T>();

templatedProp<T>("first shrink is min",
[](const GenParams &params) {
// TODO range generator
Expand Down Expand Up @@ -249,14 +270,6 @@ struct InRangeProperties {
RC_FAIL("Gave up");
});

templatedProp<T>("when size == 0, generates only min",
[](const Random &random) {
const auto range = *genRange;
RC_ASSERT(
gen::inRange(range.first, range.second)(random, 0) ==
shrinkable::just(range.first));
});

templatedProp<T>("finds shrink where value must be larger than some value",
[](const Random &random) {
const auto range = *genRange;
Expand All @@ -273,8 +286,54 @@ struct InRangeProperties {
}
};

struct InRangeRealProperties {
template <typename T>
static void exec() {

static const auto genRange = genRangeOf<T>();

templatedProp<T>(
"spreads values over the whole range",
[](const Random &random) {
// Unlike the integral case a continuous range cannot be covered
// exhaustively, so check that every decile gets hit instead.
const auto min = *gen::inRange<T>(-1000, 1000);
const auto max = min + *gen::inRange<T>(1, 1000);

const auto gen = gen::inRange<T>(min, max);
auto r = random;
std::vector<int> counts(10, 0);
for (std::size_t i = 0; i < 100000; i++) {
const auto x = gen(r.split(), kNominalSize).value();
const auto decile = static_cast<std::size_t>(
(10 * (x - min)) / (max - min));
counts[std::min<std::size_t>(decile, 9)]++;
if (std::find(begin(counts), end(counts), 0) == end(counts)) {
RC_SUCCEED("All deciles generated");
}
}

RC_FAIL("Gave up");
});

templatedProp<T>(
"shrinks towards min",
[](const GenParams &params) {
const auto range = *genRange;
const auto shrinkable =
gen::inRange<T>(range.first, range.second)(params.random,
params.size);
const auto result = shrinkable::findLocalMin(
shrinkable, [](T) { return true; });
RC_ASSERT(result.first == range.first);
});
}
};

} // namespace

TEST_CASE("gen::inRange") {
forEachType<InRangeProperties, RC_INTEGRAL_TYPES>();
forEachType<InRangeProperties, RC_NUMERIC_TYPES>();
forEachType<InRangeIntegralProperties, RC_INTEGRAL_TYPES>();
forEachType<InRangeRealProperties, RC_REAL_TYPES>();
}
55 changes: 48 additions & 7 deletions test/shrink/ShrinkTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,28 @@ TEST_CASE("shrink::eachElement") {
namespace {

struct ShrinkTowardsProperties {
template <typename T>
static void exec() {
templatedProp<T>(
"shrinking towards self yields empty shrink",
[](T target) { RC_ASSERT(!shrink::towards(target, target).next()); });

templatedProp<T>(
"never contains original value",
[](T x, T y) { RC_ASSERT(!seq::contains(shrink::towards(x, y), x)); });

templatedProp<T>("never leaves the interval between target and value",
[](T target) {
T value = *gen::distinctFrom(target);
const auto lo = std::min(value, target);
const auto hi = std::max(value, target);
seq::forEach(shrink::towards(value, target),
[=](T x) { RC_ASSERT(x >= lo && x <= hi); });
});
}
};

struct ShrinkTowardsIntegralProperties {
template <typename T>
static void exec() {
templatedProp<T>("first tries target immediately",
Expand All @@ -142,21 +164,40 @@ struct ShrinkTowardsProperties {
(value > target) ? (value - *fin) : (*fin - value);
RC_ASSERT(diff == T(1));
});
}
};

templatedProp<T>(
"shrinking towards self yields empty shrink",
[](T target) { RC_ASSERT(!shrink::towards(target, target).next()); });
struct ShrinkTowardsRealProperties {
template <typename T>
static void exec() {
templatedProp<T>("each shrink is closer to the target than the last",
[](T target) {
T value = *gen::distinctFrom(target);
T previous = value;
seq::forEach(shrink::towards(value, target),
[&](T x) {
RC_ASSERT(std::abs(x - target) <
std::abs(previous - target));
previous = x;
});
});

templatedProp<T>(
"never contains original value",
[](T x, T y) { RC_ASSERT(!seq::contains(shrink::towards(x, y), x)); });
templatedProp<T>("reaches the target",
[](T target) {
T value = *gen::distinctFrom(target);
const auto fin = seq::last(shrink::towards(value, target));
RC_ASSERT(fin);
RC_ASSERT(*fin == target);
});
}
};

} // namespace

TEST_CASE("shrink::towards") {
forEachType<ShrinkTowardsProperties, RC_INTEGRAL_TYPES>();
forEachType<ShrinkTowardsProperties, RC_NUMERIC_TYPES>();
forEachType<ShrinkTowardsIntegralProperties, RC_INTEGRAL_TYPES>();
forEachType<ShrinkTowardsRealProperties, RC_REAL_TYPES>();
}

namespace {
Expand Down