class SatCounter
{
public:
+ /** The default constructor should never be used. */
+ SatCounter() = delete;
+
/**
* Constructor for the counter. The explicit keyword is used to make
* sure the user does not assign a number to the counter thinking it
"Saturating counter's Initial value exceeds max value.");
}
+ /** Copy constructor. */
+ SatCounter(const SatCounter& other)
+ : initialVal(other.initialVal), maxVal(other.maxVal),
+ counter(other.counter)
+ {
+ }
+
+ /** Copy assignment. */
+ SatCounter& operator=(const SatCounter& other) {
+ if (this != &other) {
+ SatCounter temp(other);
+ this->swap(temp);
+ }
+ return *this;
+ }
+
+ /** Move constructor. */
+ SatCounter(SatCounter&& other)
+ {
+ initialVal = other.initialVal;
+ maxVal = other.maxVal;
+ counter = other.counter;
+ SatCounter temp(0);
+ other.swap(temp);
+ }
+
+ /** Move assignment. */
+ SatCounter& operator=(SatCounter&& other) {
+ if (this != &other) {
+ initialVal = other.initialVal;
+ maxVal = other.maxVal;
+ counter = other.counter;
+ SatCounter temp(0);
+ other.swap(temp);
+ }
+ return *this;
+ }
+
+ /**
+ * Swap the contents of every member of the class. Used for the default
+ * copy-assignment created by the compiler.
+ *
+ * @param other The other object to swap contents with.
+ */
+ void
+ swap(SatCounter& other)
+ {
+ std::swap(initialVal, other.initialVal);
+ std::swap(maxVal, other.maxVal);
+ std::swap(counter, other.counter);
+ }
+
/** Pre-increment operator. */
SatCounter&
operator++()
return old_counter;
}
+ /** Shift-right-assignment. */
+ SatCounter&
+ operator>>=(const int& shift)
+ {
+ this->counter >>= shift;
+ return *this;
+ }
+
+ /** Shift-left-assignment. */
+ SatCounter&
+ operator<<=(const int& shift)
+ {
+ this->counter <<= shift;
+ if (this->counter > maxVal) {
+ this->counter = maxVal;
+ }
+ return *this;
+ }
+
+ /** Add-assignment. */
+ SatCounter&
+ operator+=(const int& value)
+ {
+ if (maxVal - this->counter >= value) {
+ this->counter += value;
+ } else {
+ this->counter = maxVal;
+ }
+ return *this;
+ }
+
+ /** Subtract-assignment. */
+ SatCounter&
+ operator-=(const int& value)
+ {
+ if (this->counter > value) {
+ this->counter -= value;
+ } else {
+ this->counter = 0;
+ }
+ return *this;
+ }
+
/**
* Read the counter's value.
*/
/** Reset the counter to its initial value. */
void reset() { counter = initialVal; }
+ /**
+ * Calculate saturation percentile of the current counter's value
+ * with regard to its maximum possible value.
+ *
+ * @return A value between 0.0 and 1.0 to indicate which percentile of
+ * the maximum value the current value is.
+ */
+ double calcSaturation() const { return (double) counter / maxVal; }
+
+ /**
+ * Whether the counter has achieved its maximum value or not.
+ *
+ * @return True if the counter saturated.
+ */
+ bool isSaturated() const { return counter == maxVal; }
+
private:
uint8_t initialVal;
uint8_t maxVal;
#include <gtest/gtest.h>
+#include <utility>
+
#include "base/sat_counter.hh"
/**
ASSERT_EQ(counter, initial_value);
}
+/**
+ * Test calculating saturation percentile.
+ */
+TEST(SatCounterTest, SaturationPercentile)
+{
+ const unsigned bits = 3;
+ const unsigned max_value = (1 << bits) - 1;
+ SatCounter counter(bits);
+
+ ASSERT_FALSE(counter.isSaturated());
+ for (double value = 0.0; value <= max_value; value++, counter++) {
+ const double saturation = value / max_value;
+ ASSERT_DOUBLE_EQ(counter.calcSaturation(), saturation);
+ }
+ ASSERT_TRUE(counter.isSaturated());
+}
+
/**
* Test back and forth against an int.
*/
ASSERT_EQ(counter, 0);
}
+/**
+ * Test shift operators.
+ */
+TEST(SatCounterTest, Shift)
+{
+ const unsigned bits = 3;
+ const unsigned max_value = (1 << bits) - 1;
+ const unsigned initial_value = 1;
+ SatCounter counter(bits, initial_value);
+ SatCounter other(bits, initial_value);
+ // The saturated shift value is just enough to saturate, since greater
+ // values could generate undefined behavior
+ SatCounter saturated_counter(bits, bits);
+ int value = initial_value;
+
+ // Test random shifts
+ counter <<= 2;
+ value <<= 2;
+ ASSERT_EQ(counter, value);
+ counter >>= 1;
+ value >>= 1;
+ ASSERT_EQ(counter, value);
+
+ // Test saturation
+ counter <<= bits;
+ ASSERT_EQ(counter, max_value);
+
+ // Test zeroing
+ counter >>= bits;
+ ASSERT_EQ(counter, 0);
+
+ // Test saturation against other saturating counter
+ counter.reset();
+ value = initial_value;
+ counter <<= other;
+ value <<= other;
+ ASSERT_EQ(counter, value);
+ counter <<= saturated_counter;
+ value = max_value;
+ ASSERT_EQ(counter, max_value);
+
+ // Test zeroing against other saturating counter
+ counter >>= other;
+ value >>= other;
+ ASSERT_EQ(counter, value);
+ counter >>= saturated_counter;
+ ASSERT_EQ(counter, 0);
+}
+
/**
* Test both pre and post operators.
*/
ASSERT_EQ(counter_pre, 0);
ASSERT_EQ(counter_post, 0);
}
+
+/**
+ * Test copy and move for both constructor and assignment.
+ */
+TEST(SatCounterTest, CopyMove)
+{
+ const unsigned bits = 3;
+ const unsigned max_value = (1 << bits) - 1;
+ const unsigned initial_value = 1;
+ SatCounter counter(bits, initial_value);
+ SatCounter deep_copy(1);
+ SatCounter counter_copy(2);
+
+ // Increase counter value so that we can check if the inner counter is
+ // being copied
+ counter++;
+
+ // Copy counter using both the copy constructor and the copy assignment
+ SatCounter counter_copy_constructor(counter);
+ deep_copy = counter_copy = counter;
+ ASSERT_EQ(counter_copy_constructor, initial_value + 1);
+ ASSERT_EQ(counter_copy, initial_value + 1);
+ ASSERT_EQ(deep_copy, initial_value + 1);
+
+ // Make sure max value is the same for all of them, and that modifying
+ // the copies does not modify the original
+ for (int i = 0; i < 2*max_value; i++) {
+ counter_copy_constructor++;
+ counter_copy++;
+ deep_copy++;
+ }
+ ASSERT_EQ(counter, initial_value + 1);
+ ASSERT_EQ(counter_copy_constructor, max_value);
+ ASSERT_EQ(counter_copy, max_value);
+ ASSERT_EQ(deep_copy, max_value);
+
+ // Make sure initial value is the same for all of them
+ counter_copy_constructor.reset();
+ counter_copy.reset();
+ deep_copy.reset();
+ ASSERT_EQ(counter_copy_constructor, initial_value);
+ ASSERT_EQ(counter_copy, initial_value);
+ ASSERT_EQ(deep_copy, initial_value);
+
+ // Now check move
+ SatCounter counter_move_constructor(std::move(counter));
+ ASSERT_EQ(counter, 0);
+ ASSERT_EQ(counter_move_constructor, initial_value + 1);
+
+ SatCounter counter_move(bits);
+ counter_move = std::move(counter_move_constructor);
+ ASSERT_EQ(counter_move_constructor, 0);
+ ASSERT_EQ(counter_move, initial_value + 1);
+}
+
+/**
+ * Test add-assignment and subtract assignment.
+ */
+TEST(SatCounterTest, AddSubAssignment)
+{
+ const unsigned bits = 3;
+ const unsigned max_value = (1 << bits) - 1;
+ SatCounter counter(bits);
+ SatCounter other(bits, 2);
+ SatCounter saturated_counter(bits, max_value);
+ int value = 0;
+
+ // Test add-assignment for a few random values and then saturate
+ counter += 2;
+ value += 2;
+ ASSERT_EQ(counter, value);
+ counter += 3;
+ value += 3;
+ ASSERT_EQ(counter, value);
+ counter += max_value;
+ value = max_value;
+ ASSERT_EQ(counter, value);
+
+ // Test subtract-assignment for a few random values until back to zero
+ counter -= 2;
+ value -= 2;
+ ASSERT_EQ(counter, value);
+ counter -= 3;
+ value -= 3;
+ ASSERT_EQ(counter, value);
+ counter -= max_value;
+ value = 0;
+ ASSERT_EQ(counter, value);
+
+ // Test add-assignment of other saturating counter
+ counter += other;
+ value += other;
+ ASSERT_EQ(counter, value);
+ counter += saturated_counter;
+ value = max_value;
+ ASSERT_EQ(counter, saturated_counter);
+
+ // Test subtract-assignment of other saturating counter
+ counter -= other;
+ value -= other;
+ ASSERT_EQ(counter, value);
+ counter -= saturated_counter;
+ ASSERT_EQ(counter, 0);
+}
+