Browse Source

breaking: add support for signed int, change interface for Device

Sam Jaffe 2 years ago
parent
commit
0c222da4b1
9 changed files with 187 additions and 170 deletions
  1. 2 2
      include/random/device.h
  2. 11 6
      include/random/random.h
  3. 12 15
      include/random/tape.h
  4. 4 4
      include/random/thread_safe.h
  5. 34 21
      src/random.cxx
  6. 26 48
      src/tape.cxx
  7. 7 6
      src/thread_safe.cxx
  8. 32 16
      test/random_test.cxx
  9. 59 52
      test/tape_test.cxx

+ 2 - 2
include/random/device.h

@@ -10,8 +10,8 @@
 namespace engine::random {
 struct Device {
   virtual ~Device() = default;
-  virtual unsigned int exclusive(unsigned int max) = 0;
+  virtual int32_t inclusive(int32_t min, int32_t max) = 0;
+  virtual uint32_t inclusive(uint32_t min, uint32_t max) = 0;
   virtual double exclusive(double min, double max) = 0;
-  virtual double inclusive(double min, double max) = 0;
 };
 }

+ 11 - 6
include/random/random.h

@@ -13,17 +13,22 @@ private:
 
 public:
   Random();
-  Random(Random const &other, Tape & with_tape);
-  Random(Random const &other, std::shared_ptr<Tape> with_tape);
+  Random(Random const & other, Tape & with_tape);
+  Random(Random const & other, std::shared_ptr<Tape> with_tape);
   template <typename P> Random(P const & p) : impl_(std::make_shared<P>(p)) {}
   template <typename P> Random(std::shared_ptr<P> const & p) : impl_(p) {}
-  
-  unsigned int exclusive(unsigned int max) const;
+
+  int32_t exclusive(int32_t min, int32_t max) const;
+  int32_t inclusive(int32_t min, int32_t max) const;
+
+  uint32_t exclusive(uint32_t min, uint32_t max) const;
+  uint32_t inclusive(uint32_t min, uint32_t max) const;
+
   double exclusive(double min, double max) const;
   double inclusive(double min, double max) const;
-  
+
   std::shared_ptr<Device> device() const { return impl_; }
-  
+
   /**
    * Create a scope in which all calls to the generator functions will record
    * their results into a tape. Expects that there is no tape currently set.

+ 12 - 15
include/random/tape.h

@@ -20,10 +20,9 @@ class Tape : public Device {
 public:
   // Allow for the serialization and de-serialization of this Tape
   using serial_type =
-      std::vector<std::tuple<bool, std::string, uint64_t, uint64_t, uint64_t>>;
+      std::vector<std::tuple<std::string, uint64_t, uint64_t, uint64_t>>;
 
 private:
-  enum class Type : char;
   template <typename T> struct EntryT {
     EntryT(T min, T max) : min(min), max(max), result() {}
     EntryT(T min, T max, T result) : min(min), max(max), result(result) {}
@@ -33,14 +32,8 @@ private:
     T max;
     T result;
   };
-  
-  struct Entry {
-    template <typename T>
-    Entry(Type type, EntryT<T> params) : type(type), params(params) {}
 
-    Type type;
-    std::variant<EntryT<double>, EntryT<unsigned int>> params;
-  };
+  using Entry = std::variant<EntryT<double>, EntryT<uint32_t>, EntryT<int32_t>>;
 
 private:
   size_t index_{0}; // transient
@@ -51,15 +44,19 @@ public:
   Tape(serial_type serial);
   explicit operator serial_type() const;
 
-  unsigned int exclusive(unsigned int max) override;
-  double exclusive(double min, double max) override;
-  double inclusive(double min, double max) override;
+  int32_t inclusive(int32_t min, int32_t max) override {
+    return fetch(min, max);
+  }
+  uint32_t inclusive(uint32_t min, uint32_t max) override {
+    return fetch(min, max);
+  }
+  double exclusive(double min, double max) override { return fetch(min, max); }
 
-  void exclusive(unsigned int max, unsigned int result);
+  void inclusive(int32_t min, int32_t max, int32_t result);
+  void inclusive(uint32_t min, uint32_t max, uint32_t result);
   void exclusive(double min, double max, double result);
-  void inclusive(double min, double max, double result);
 
 private:
-  template <typename T> T fetch(Type type, T min, T max);
+  template <typename T> T fetch(T min, T max);
 };
 }

+ 4 - 4
include/random/thread_safe.h

@@ -18,13 +18,13 @@ class ThreadSafeDevice : public Device {
 private:
   std::mutex mutex_;
   std::unique_ptr<Device> impl_;
-  
+
 public:
   ThreadSafeDevice(std::unique_ptr<Device> impl);
-  
-  unsigned int exclusive(unsigned int max) final;
+
+  int32_t inclusive(int32_t min, int32_t max) final;
+  uint32_t inclusive(uint32_t min, uint32_t max) final;
   double exclusive(double min, double max) final;
-  double inclusive(double min, double max) final;
 };
 
 }

+ 34 - 21
src/random.cxx

@@ -9,28 +9,26 @@
 #include "random/device.h"
 #include "random/tape.h"
 
-#define EVALUATE(func, ...)                                                   \
-  [&, this]() {                                                               \
-    auto result = impl_->func(__VA_ARGS__);                                   \
-    if (tape_) { tape_->func(__VA_ARGS__, result); }                          \
-    return result;                                                            \
+#define EVALUATE(func, ...)                                                    \
+  [&, this]() {                                                                \
+    auto result = impl_->func(__VA_ARGS__);                                    \
+    if (tape_) { tape_->func(__VA_ARGS__, result); }                           \
+    return result;                                                             \
   }()
 
 namespace engine::random {
 class DefaultDevice : public Device {
 public:
-  unsigned int exclusive(unsigned int max) override {
-    assert(max != 0);
-    return uniform(0, max - 1)(rng);
+  int32_t inclusive(int32_t min, int32_t max) override {
+    return std::uniform_int_distribution<>(min, max)(rng);
   }
 
-  double exclusive(double min, double max) override {
-    return uniform_real(min, max)(rng);
+  uint32_t inclusive(uint32_t min, uint32_t max) override {
+    return std::uniform_int_distribution<uint32_t>(min, max)(rng);
   }
 
-  double inclusive(double min, double max) override {
-    double real_max = std::nextafter(max, std::numeric_limits<double>::max());
-    return std::min(uniform_real(min, real_max)(rng), max);
+  double exclusive(double min, double max) override {
+    return uniform_real(min, max)(rng);
   }
 
 private:
@@ -41,26 +39,41 @@ private:
   engine rng{std::random_device{}()};
 };
 
-Random::Random()
-    : impl_(std::make_shared<DefaultDevice>()) {}
+Random::Random() : impl_(std::make_shared<DefaultDevice>()) {}
 
-Random::Random(Random const &other, std::shared_ptr<Tape> with_tape)
+Random::Random(Random const & other, std::shared_ptr<Tape> with_tape)
     : impl_(other.impl_), tape_(with_tape) {}
 
-Random::Random(Random const &other, Tape & with_tape)
-    : impl_(other.impl_), tape_(&with_tape, [](void*){}) {}
+Random::Random(Random const & other, Tape & with_tape)
+    : impl_(other.impl_), tape_(&with_tape, [](void *) {}) {}
 
-unsigned int Random::exclusive(unsigned int max) const {
+int32_t Random::exclusive(int32_t min, int32_t max) const {
+  return inclusive(min, max - 1);
+}
+
+int32_t Random::inclusive(int32_t min, int32_t max) const {
+  assert(min < max);
+  return EVALUATE(inclusive, min, max);
+}
+
+uint32_t Random::exclusive(uint32_t min, uint32_t max) const {
   assert(max != 0);
-  return EVALUATE(exclusive, max);
+  return inclusive(min, max - 1);
+}
+
+uint32_t Random::inclusive(uint32_t min, uint32_t max) const {
+  assert(min < max);
+  return EVALUATE(inclusive, min, max);
 }
 
 double Random::exclusive(double min, double max) const {
+  assert(min < max);
   return EVALUATE(exclusive, min, max);
 }
 
 double Random::inclusive(double min, double max) const {
-  return EVALUATE(inclusive, min, max);
+  double real_max = std::nextafter(max, std::numeric_limits<double>::max());
+  return std::min(exclusive(min, real_max), max);
 }
 
 scope_exit Random::record(std::shared_ptr<Tape> tape) {

+ 26 - 48
src/tape.cxx

@@ -13,79 +13,57 @@
 #define IS_A(T) tname == typeid(T).name()
 
 namespace {
-uint64_t cast(double d) { return reinterpret_cast<uint64_t&>(d); }
-uint64_t cast(unsigned int j) { return j; }
+uint64_t r(uint32_t j) { return j; }
+uint64_t r(int32_t i) { return static_cast<uint64_t>(i); }
+uint64_t r(double d) { return reinterpret_cast<uint64_t &>(d); }
+double r(uint64_t ul) { return reinterpret_cast<double &>(ul); }
 }
 
 namespace engine::random {
-enum class Tape::Type : char { Inclusive, Exclusive };
-
-template <>
-Tape::EntryT<unsigned int>::EntryT(uint64_t min, uint64_t max, uint64_t result)
-    : min(static_cast<unsigned int>(min)),
-      max(static_cast<unsigned int>(max)),
-      result(static_cast<unsigned int>(result))
-{}
-
-template <>
-Tape::EntryT<double>::EntryT(uint64_t min, uint64_t max, uint64_t result)
-    : min(reinterpret_cast<double&>(min)),
-      max(reinterpret_cast<double&>(max)),
-      result(reinterpret_cast<double&>(result))
-{}
+template <typename T>
+Tape::EntryT<T>::EntryT(uint64_t min, uint64_t max, uint64_t result)
+    : min(static_cast<T>(min)), max(static_cast<T>(max)),
+      result(static_cast<T>(result)) {}
 
 Tape::Tape(serial_type serial) {
-  for (auto const & [exclusive, tname, min, max, result] : serial) {
-    Type const type = exclusive ? Type::Exclusive : Type::Inclusive;
-    if (IS_A(unsigned int)) {
-      entries_.emplace_back(type, EntryT<unsigned int>(min, max, result));
+  for (auto const & [tname, min, max, result] : serial) {
+    if (IS_A(uint32_t)) {
+      entries_.emplace_back(EntryT<uint32_t>(min, max, result));
+    } else if (IS_A(int32_t)) {
+      entries_.emplace_back(EntryT<int32_t>(min, max, result));
     } else {
-      entries_.emplace_back(type, EntryT<double>(min, max, result));
+      entries_.emplace_back(EntryT<double>(r(min), r(max), r(result)));
     }
   }
 }
 
 Tape::operator serial_type() const {
   serial_type rval;
-  auto insert = [&rval](auto type, auto const & entry) {
-    rval.emplace_back(type == Type::Exclusive, typeid(entry.min).name(),
-                      cast(entry.min), cast(entry.max), cast(entry.result));
+  auto insert = [&rval](auto const & e) {
+    rval.emplace_back(typeid(e.min).name(), r(e.min), r(e.max), r(e.result));
   };
-  for (auto const & [type, entry] : entries_) {
-    std::visit(insert, std::variant<Type>(type), entry);
+  for (auto const & entry : entries_) {
+    std::visit(insert, entry);
   }
   return rval;
 }
 
-unsigned int Tape::exclusive(unsigned int max) {
-  return fetch(Type::Exclusive, 0u, max);
-}
-
-double Tape::exclusive(double min, double max) {
-  return fetch(Type::Exclusive, min, max);
+void Tape::inclusive(int32_t min, int32_t max, int32_t result) {
+  entries_.emplace_back(EntryT(min, max, result));
 }
 
-double Tape::inclusive(double min, double max) {
-  return fetch(Type::Inclusive, min, max);
-}
-
-void Tape::exclusive(unsigned int max, unsigned int result) {
-  entries_.emplace_back(Type::Exclusive, EntryT(0u, max, result));
+void Tape::inclusive(uint32_t min, uint32_t max, uint32_t result) {
+  entries_.emplace_back(EntryT(min, max, result));
 }
 
 void Tape::exclusive(double min, double max, double result) {
-  entries_.emplace_back(Type::Exclusive, EntryT(min, max, result));
-}
-
-void Tape::inclusive(double min, double max, double result) {
-  entries_.emplace_back(Type::Inclusive, EntryT(min, max, result));
+  entries_.emplace_back(EntryT(min, max, result));
 }
 
-template <typename T> T Tape::fetch(Type type, T min, T max) {
+template <typename T> T Tape::fetch(T min, T max) {
   expects(index_ < entries_.size(), std::out_of_range, "End of Tape");
-  auto const * entry = std::get_if<EntryT<T>>(&entries_[index_].params);
-  expects(entries_[index_].type == type && entry, std::domain_error,
-          "Mismatched signature to Device on Tape");
+  auto const * entry = std::get_if<EntryT<T>>(&entries_[index_]);
+  expects(entry, std::domain_error, "Mismatched signature to Device on Tape");
   expects(entry->min == min && entry->max == max,
           "Mismatched bounds to Device on Tape");
   ++index_;

+ 7 - 6
src/thread_safe.cxx

@@ -9,20 +9,21 @@
 #include "random/thread_safe.h"
 
 namespace engine::random {
-ThreadSafeDevice::ThreadSafeDevice(std::unique_ptr<Device> impl) : impl_(std::move(impl)) {}
+ThreadSafeDevice::ThreadSafeDevice(std::unique_ptr<Device> impl)
+    : impl_(std::move(impl)) {}
 
-unsigned int ThreadSafeDevice::exclusive(unsigned int max) {
+int32_t ThreadSafeDevice::inclusive(int32_t min, int32_t max) {
   std::lock_guard lock(mutex_);
-  return impl_->exclusive(max);
+  return impl_->exclusive(min, max);
 }
 
-double ThreadSafeDevice::exclusive(double min, double max) {
+uint32_t ThreadSafeDevice::inclusive(uint32_t min, uint32_t max) {
   std::lock_guard lock(mutex_);
   return impl_->exclusive(min, max);
 }
 
-double ThreadSafeDevice::inclusive(double min, double max) {
+double ThreadSafeDevice::exclusive(double min, double max) {
   std::lock_guard lock(mutex_);
-  return impl_->inclusive(min, max);
+  return impl_->exclusive(min, max);
 }
 }

+ 32 - 16
test/random_test.cxx

@@ -12,33 +12,48 @@
 
 #include "xcode_gtest_helper.h"
 
+using testing::DoubleNear;
+
 struct MockDevice : engine::random::Device {
-  MOCK_METHOD1(exclusive, uint32_t(uint32_t));
+  MOCK_METHOD2(s_inclusive, int32_t(int32_t, int32_t));
+  MOCK_METHOD2(u_inclusive, uint32_t(uint32_t, uint32_t));
   MOCK_METHOD2(exclusive, double(double, double));
-  MOCK_METHOD2(inclusive, double(double, double));
+
+  int32_t inclusive(int32_t min, int32_t max) { return s_inclusive(min, max); }
+  uint32_t inclusive(uint32_t min, uint32_t max) {
+    return u_inclusive(min, max);
+  }
 };
 
-TEST(RandomTest, PassesThroughExclusiveIntegerCall) {
+TEST(RandomTest, ExclusiveIntegerPassedAsInclusive) {
   auto mock = std::make_shared<MockDevice>();
   engine::random::Random generator{mock};
-  
-  EXPECT_CALL(*mock, exclusive(10)).Times(1);
-  generator.exclusive(10);
+
+  EXPECT_CALL(*mock, s_inclusive(-10, 9)).Times(1);
+  generator.exclusive(-10, 10);
+}
+
+TEST(RandomTest, ExclusiveUIntegerPassedAsInclusive) {
+  auto mock = std::make_shared<MockDevice>();
+  engine::random::Random generator{mock};
+
+  EXPECT_CALL(*mock, u_inclusive(0, 9)).Times(1);
+  generator.exclusive(0u, 10u);
 }
 
 TEST(RandomTest, PassesThroughExclusiveDoubleCall) {
   auto mock = std::make_shared<MockDevice>();
   engine::random::Random generator{mock};
-  
+
   EXPECT_CALL(*mock, exclusive(1.0, 10.0)).Times(1);
   generator.exclusive(1.0, 10.0);
 }
 
-TEST(RandomTest, PassesThroughInclusiveDoubleCall) {
+TEST(RandomTest, DoctorsInclusiveDoubleCall) {
   auto mock = std::make_shared<MockDevice>();
   engine::random::Random generator{mock};
-  
-  EXPECT_CALL(*mock, inclusive(1.0, 10.0)).Times(1);
+
+  EXPECT_CALL(*mock, exclusive(1.0, DoubleNear(10.0, 1e-7))).Times(1);
   generator.inclusive(1.0, 10.0);
 }
 
@@ -46,10 +61,11 @@ class kahan_summation {
 private:
   double sum_{0.0};
   double carry_{0.0};
+
 public:
   kahan_summation() = default;
   kahan_summation & operator+=(double d);
-  
+
   explicit operator double() const { return sum_; }
 };
 
@@ -67,10 +83,10 @@ TEST(DefaultRandomTest, RandomDistributionIntIsUniform) {
   size_t const iters = 2000000;
   for (size_t i = 0; i < iters; ++i) {
     // All values from 0 - 100 are allowable
-    sum += generator.exclusive(101);
+    sum += generator.inclusive(0, 100);
   }
   // Expected result is (0+99)/2 +/- 0.1%
-  EXPECT_THAT(double(sum) / iters, testing::DoubleNear(50, 0.05));
+  EXPECT_THAT(double(sum) / iters, DoubleNear(50, 0.05));
 }
 
 TEST(DefaultRandomTest, RandomDistributionDblIsUniform) {
@@ -82,12 +98,12 @@ TEST(DefaultRandomTest, RandomDistributionDblIsUniform) {
     sum += generator.exclusive(0.0, 100.0);
   }
   // Expected result is [0.0, 100.0) +/- 0.1%
-  EXPECT_THAT(double(sum) / iters, testing::DoubleNear(50, 0.05));
+  EXPECT_THAT(double(sum) / iters, DoubleNear(50, 0.05));
 }
 
 TEST(DefaultRandomTest, InclusiveRangeMayIncludeValue) {
   engine::random::Random generator{};
-  EXPECT_THAT(generator.inclusive(1.0, 1.0), testing::DoubleNear(1.0, 1E-6));
+  EXPECT_THAT(generator.inclusive(1.0, 1.0), DoubleNear(1.0, 1E-6));
 }
 
 TEST(DefaultRandomTest, RandomDistributionDblInclIsUniform) {
@@ -99,5 +115,5 @@ TEST(DefaultRandomTest, RandomDistributionDblInclIsUniform) {
     sum += generator.inclusive(0.0, 100.0);
   }
   // Expected result is (0+100)/2 +/- 0.1%
-  EXPECT_THAT(double(sum) / iters, testing::DoubleNear(50, 0.05));
+  EXPECT_THAT(double(sum) / iters, DoubleNear(50, 0.05));
 }

+ 59 - 52
test/tape_test.cxx

@@ -8,6 +8,10 @@
 
 #include "random/tape.h"
 
+#include <cstdlib>
+
+#include <scope_guard/scope_guard.hpp>
+
 #include "random/random.h"
 
 #include "xcode_gtest_helper.h"
@@ -15,47 +19,48 @@
 using testing::ElementsAre;
 using testing::FieldsAre;
 
+MATCHER_P(BytesAre, value, "") {
+  *result_listener << "whose bytes are " << std::hex << arg;
+  return reinterpret_cast<double const &>(arg) == value;
+}
+
 TEST(TapeTest, CanRecordIntData) {
   engine::random::Tape tape;
-  
-  tape.exclusive(100u, 50);
-  EXPECT_EQ(tape.exclusive(100), 50);
+
+  tape.inclusive(0u, 100u, 50u);
+  EXPECT_EQ(tape.inclusive(0u, 100u), 50);
 }
 
 TEST(TapeTest, CanRecordDoubleData) {
   engine::random::Tape tape;
-  
+
   tape.exclusive(0.0, 100.0, 50.0);
   EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
 
-  tape.inclusive(0.0, 100.0, 50.0);
-  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
+  tape.exclusive(0.0, 100.0, 50.0);
+  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
 }
 
 TEST(TapeTest, ThrowsOnOverFetch) {
   engine::random::Tape tape;
-  tape.exclusive(100u, 50);
-  EXPECT_EQ(tape.exclusive(100), 50);
-  EXPECT_THROW(tape.exclusive(100), std::out_of_range);
+  tape.inclusive(0, 100, 50);
+  EXPECT_EQ(tape.inclusive(0, 100), 50);
+  EXPECT_THROW(tape.inclusive(0, 100), std::out_of_range);
 }
 
 TEST(TapeTest, BadRequestIsNoOp) {
   engine::random::Tape tape;
-  tape.exclusive(100u, 50);
-  EXPECT_ANY_THROW(tape.exclusive(1));
-  EXPECT_EQ(tape.exclusive(100), 50);
+  tape.inclusive(0, 100, 50);
+  EXPECT_ANY_THROW(tape.inclusive(0, 1));
+  EXPECT_EQ(tape.inclusive(0, 100), 50);
 }
 
 TEST(TapeTest, ThrowsOnTypeMismatch) {
   engine::random::Tape tape;
-  tape.exclusive(100u, 50);
+  tape.inclusive(0u, 100u, 50u);
   EXPECT_THROW(tape.exclusive(0.0, 100.0), std::domain_error);
-}
-
-TEST(TapeTest, ThrowsOnInclusiveExclusiveMismatch) {
-  engine::random::Tape tape;
-  tape.exclusive(0.0, 100.0, 50.0);
-  EXPECT_THROW(tape.inclusive(0.0, 100.0), std::domain_error);
+  EXPECT_THROW(tape.inclusive(0, 100), std::domain_error);
+  EXPECT_NO_THROW(tape.inclusive(0u, 100u));
 }
 
 TEST(TapeTest, ThrowsOnBoundsMismatch) {
@@ -67,49 +72,51 @@ TEST(TapeTest, ThrowsOnBoundsMismatch) {
 TEST(TapeTest, IsOrdered) {
   engine::random::Tape tape;
   tape.exclusive(0.0, 100.0, 50.0);
-  tape.inclusive(0.0, 100.0, 50.0);
-  
-  EXPECT_THROW(tape.inclusive(0.0, 100.0), std::domain_error);
+  tape.exclusive(10.0, 90.0, 50.0);
+
+  EXPECT_THROW(tape.exclusive(10.0, 90.0), std::logic_error);
 
   EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
-  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
+  EXPECT_EQ(tape.exclusive(10.0, 90.0), 50.0);
 }
 
 TEST(TapeTest, IsSerializable) {
   engine::random::Tape tape;
-  tape.exclusive(100u, 50);
+  tape.inclusive(0, 100, 50);
+  tape.inclusive(0u, 100u, 50u);
   tape.exclusive(0.0, 100.0, 50.0);
-  tape.inclusive(0.0, 100.0, 50.0);
-  
+
   auto serial = engine::random::Tape::serial_type(tape);
-  EXPECT_THAT(serial, ElementsAre(FieldsAre(true, "j", 0, 100, 50),
-                                  FieldsAre(true, "d", 0, 100, 50),
-                                  FieldsAre(false, "d", 0, 100, 50)));
-  
+  EXPECT_THAT(
+      serial,
+      ElementsAre(FieldsAre("i", 0, 100, 50), FieldsAre("j", 0, 100, 50),
+                  FieldsAre("d", BytesAre(0), BytesAre(100), BytesAre(50))));
+
   tape = engine::random::Tape(serial);
-  EXPECT_EQ(tape.exclusive(100), 50);
+  EXPECT_EQ(tape.inclusive(0, 100), 50);
+  EXPECT_EQ(tape.inclusive(0u, 100u), 50u);
   EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
-  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
 }
 
 TEST(TapeTest, IndexNotIncludedInSerialization) {
   engine::random::Tape tape;
-  tape.exclusive(100u, 50);
+  tape.inclusive(0, 100, 50);
+  tape.inclusive(0u, 100u, 50u);
   tape.exclusive(0.0, 100.0, 50.0);
-  tape.inclusive(0.0, 100.0, 50.0);
-  
-  EXPECT_EQ(tape.exclusive(100), 50);
-  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
-  
+
+  EXPECT_EQ(tape.inclusive(0, 100), 50);
+  EXPECT_EQ(tape.inclusive(0u, 100u), 50u);
+
   auto serial = engine::random::Tape::serial_type(tape);
-  EXPECT_THAT(serial, ElementsAre(FieldsAre(true, "j", 0, 100, 50),
-                                  FieldsAre(true, "d", 0, 100, 50),
-                                  FieldsAre(false, "d", 0, 100, 50)));
-  
+  EXPECT_THAT(
+      serial,
+      ElementsAre(FieldsAre("i", 0, 100, 50), FieldsAre("j", 0, 100, 50),
+                  FieldsAre("d", BytesAre(0), BytesAre(100), BytesAre(50))));
+
   tape = engine::random::Tape(serial);
-  EXPECT_EQ(tape.exclusive(100), 50);
+  EXPECT_EQ(tape.inclusive(0, 100), 50);
+  EXPECT_EQ(tape.inclusive(0u, 100u), 50u);
   EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
-  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
 }
 
 TEST(TapeTest, CanAttachToRandom) {
@@ -117,9 +124,9 @@ TEST(TapeTest, CanAttachToRandom) {
   auto tape = std::make_shared<engine::random::Tape>();
 
   auto scope = random.record(tape);
-  
-  auto result = random.exclusive(100);
-  EXPECT_EQ(tape->exclusive(100), result);
+
+  auto result = random.inclusive(0, 100);
+  EXPECT_EQ(tape->inclusive(0, 100), result);
 }
 
 TEST(TapeTest, StopsRecordingOnScopeExit) {
@@ -128,10 +135,10 @@ TEST(TapeTest, StopsRecordingOnScopeExit) {
 
   {
     auto scope = random.record(tape);
-    random.exclusive(100);
+    random.inclusive(0, 100);
   }
-  random.exclusive(100);
-  
-  EXPECT_NO_THROW(tape->exclusive(100));
-  EXPECT_THROW(tape->exclusive(100), std::out_of_range);
+  random.inclusive(0, 100);
+
+  EXPECT_NO_THROW(tape->inclusive(0, 100));
+  EXPECT_THROW(tape->inclusive(0, 100), std::out_of_range);
 }