Working basic circular buffer with associated unit tests.

This commit is contained in:
Jerome 2021-12-18 18:42:31 +01:00
parent 1df7427f7d
commit a34fac85fc
9 changed files with 305 additions and 31 deletions

3
.gitignore vendored
View file

@ -1,5 +1,6 @@
# executable
run
CircularBuffer
CircularBuffer_test
# gcov files
*.gcov

View file

@ -6,7 +6,7 @@ BUILD_PATH = build
BIN_PATH = $(BUILD_PATH)/bin
# executable #
BIN_NAME = CppQuickStart
BIN_NAME = CircularBuffer
# extensions #
SRC_EXT = cpp

View file

@ -1,3 +1,3 @@
CppQuickStart
=============
"Empty" project for a quicker start writing something in c++.
Circular Buffer
===============
Efficient custom all-template fixed-size circular buffer in C++.

View file

@ -0,0 +1,98 @@
#ifndef DEF_CircularBuffer
#define DEF_CircularBuffer
template<size_t N, typename T>
class CircularBuffer
{
public:
/// Creates a circular buffer of size N containing items of type T, with values uninitialized.
CircularBuffer()
: data(NULL),
readPointer(0),
writePointer(0)
{ data = new T[N]; }
/// Creates a circular buffer of size N containing items of type T, initialized with the value initValue.
CircularBuffer(T const& initValue)
{
data = new T[N];
Fill(initValue);
}
/// Destroys the circular buffer.
~CircularBuffer() { delete[] data; }
void Fill(T const& initValue)
{
for(size_t i = 0 ; i < N ; i++)
data[i] = initValue;
}
/// Sets the read pointer's absolute position.
void SetReadPointer(int i) { readPointer = BoundIndex(i); }
/// Gets the read pointer's absolute position.
int GetReadPointer() const { return readPointer; }
/// Sets the write pointer's absolute position.
void SetWritePointer(int i) { writePointer = BoundIndex(i); }
/// Gets the write pointer's absolute position.
int GetWritePointer() const { return writePointer; }
/// Returns a reference to the element i in the buffer. The given index is absolute.
T & operator[](int i) { return data[i]; }
/// Returns a constant reference to the element i in the buffer. The given index is absolute.
T const& operator[](int i) const { return data[i]; }
/// Returns a reference to the element i in the buffer. The given index is relative to the read pointer.
T & operator()(int i) { return data[(readPointer + i) % (int)N]; }
/// Returns a constant reference to the element i in the buffer. The given index is relative to the read pointer.
T const& operator()(int i) const { return data[(readPointer + i) % (int)N]; }
/// Writes the value at the current write pointer's location and moves it forward by 1. If the write pointer catches back with the read pointer, false is returned.
bool WriteFwd(T const& v)
{
data[writePointer] = v;
writePointer = BoundIndex(writePointer + 1);
return writePointer != readPointer;
}
/// Writes the value at the current write pointer's location and moves it backwards by 1. If the write pointer catches back with the read pointer, false is returned.
bool WriteBck(T const& v)
{
data[writePointer] = v;
writePointer = BoundIndex(writePointer - 1);
return writePointer != readPointer;
}
/// Reads the value at the current read pointer's location and moves it forward by 1. If the read pointer is co-located with the the write pointer at the time of the read, false is returned.
bool ReadFwd(T & v)
{
v = data[readPointer];
int readPointerWhenRead = readPointer;
readPointer = BoundIndex(readPointer + 1);
return writePointer != readPointerWhenRead;
}
/// Reads the value at the current read pointer's location and moves it backwards by 1. If the read pointer is co-located with the the write pointer at the time of the read, false is returned.
bool ReadBck(T & v)
{
v = data[readPointer];
int readPointerWhenRead = readPointer;
readPointer = BoundIndex(readPointer - 1);
return writePointer != readPointerWhenRead;
}
/// Bounds the index between 0 and N;
static inline int BoundIndex(int i) { return (i >= 0) ? i % (int)N : ((int)N + (i % (int)N)) % (int)N; }
protected:
T *data;
int readPointer; //!< Read pointer location
int writePointer; //!< Write pointer location
};
#endif

View file

@ -1,6 +0,0 @@
#ifndef DEF_TestClass
#define DEF_TestClass
#endif

View file

@ -1 +0,0 @@
#include "TestClass.hpp"

View file

@ -1,7 +1,7 @@
#include <iostream>
#include <utils.hpp>
#include <TestClass.hpp>
#include <CircularBuffer.hpp>
using std::cout;
using std::endl;

View file

@ -6,7 +6,7 @@ BUILD_PATH = build
BIN_PATH = $(BUILD_PATH)/bin
# executable #
BIN_NAME = CppQuickStart_test
BIN_NAME = CircularBuffer_test
# extensions #
SRC_EXT = cpp

View file

@ -3,32 +3,214 @@
#include <utils.hpp>
#include <utils_test.hpp>
#include <CircularBuffer.hpp>
#define print(x) PRINT_VAR(x);
#define printvec(x) PRINT_VEC(x);
#define printstr(x) PRINT_STR(x);
TEST_CASE( "Test case 1", "[test1]" )
TEST_CASE( "Circular Buffer test 1", "[CircularBufferTest1]" )
{
std::cout.precision(16);
SECTION( "Check almost equal" ) {
CHECK(check_almost_equal(1.00, 1.01, 0.1));
CHECK(check_almost_equal(1.00, 3.01, 0.01));
CHECK(check_almost_equal(1.00, 1.01, 0.001));
REQUIRE(true);
SECTION( "Check BoundIndex" ) {
constexpr unsigned int N = 5;
for(int i = 0 ; i < (int)(2*N) ; i++)
CHECK(CircularBuffer<N,int>::BoundIndex(i) == (i % N));
CHECK(CircularBuffer<N,int>::BoundIndex(0) == 0);
CHECK(CircularBuffer<N,int>::BoundIndex(-1) == 4);
CHECK(CircularBuffer<N,int>::BoundIndex(-2) == 3);
CHECK(CircularBuffer<N,int>::BoundIndex(-3) == 2);
CHECK(CircularBuffer<N,int>::BoundIndex(-4) == 1);
CHECK(CircularBuffer<N,int>::BoundIndex(-5) == 0);
}
SECTION( "Check almost equal on vectors" ) {
unsigned int N = 5;
std::vector<double> v1(N), v2(N);
for (size_t i = 0; i < N; i++) {
v1[i] = double(i);
v2[i] = v1[i] + 0.0001;
}
SECTION( "Check constructors" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf(0);
CHECK(check_almost_equalV(v1, v2, 0.001));
CHECK(check_almost_equalV(v1, v2, 0.0001));
CHECK(check_almost_equalV(v1, v2, 0.00001));
for(size_t i = 0 ; i < N ; i++)
CHECK(buf[i] == 0);
}
SECTION( "Check operator[]" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
for(size_t i = 0 ; i < N ; i++)
CHECK(buf[i] == i);
}
SECTION( "Check operator()" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
buf.SetReadPointer(2);
for(size_t i = 0 ; i < N ; i++)
CHECK(buf(i) == buf[(buf.GetReadPointer() + i) % (int)N]);
}
SECTION( "Check ReadFwd() and ReadBck() nominal" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
int v;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
buf.SetWritePointer(1);
buf.SetReadPointer(2);
// read forwards
CHECK(buf.ReadFwd(v) == true);
CHECK(v == 2);
CHECK(buf.GetReadPointer() == 3);
// read backwards
CHECK(buf.ReadBck(v) == true);
CHECK(v == 3);
CHECK(buf.GetReadPointer() == 2);
}
SECTION( "Check ReadFwd() and ReadBck() error" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
int v;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
buf.SetWritePointer(2);
buf.SetReadPointer(2);
// read forwards
CHECK(buf.ReadFwd(v) == false);
CHECK(v == 2);
CHECK(buf.GetReadPointer() == 3);
// read backwards
buf.SetWritePointer(3);
CHECK(buf.ReadBck(v) == false);
CHECK(v == 3);
CHECK(buf.GetReadPointer() == 2);
}
SECTION( "Check ReadFwd() and ReadBck() wrap-around" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
int v;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
buf.SetWritePointer(0);
buf.SetReadPointer(4);
// read forwards
CHECK(buf.ReadFwd(v) == true);
CHECK(v == 4);
CHECK(buf.GetReadPointer() == 0);
// read backwards
buf.SetWritePointer(3);
CHECK(buf.ReadBck(v) == true);
CHECK(v == 0);
CHECK(buf.GetReadPointer() == 4);
}
SECTION( "Check WriteFwd() and WriteBck() nominal" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
buf.SetWritePointer(2);
buf.SetReadPointer(0);
// write forwards
CHECK(buf.WriteFwd(10) == true);
CHECK(buf.GetWritePointer() == 3);
CHECK(buf[2] == 10);
// write backwards
CHECK(buf.WriteBck(15) == true);
CHECK(buf[3] == 15);
CHECK(buf.GetWritePointer() == 2);
}
SECTION( "Check WriteFwd() and WriteBck() end-of-buffer" ) {
constexpr unsigned int N = 5;
CircularBuffer<N,int> buf;
for(size_t i = 0 ; i < N ; i++)
buf[i] = i;
buf.SetWritePointer(2);
buf.SetReadPointer(3);
// write forwards
CHECK(buf.WriteFwd(10) == false); // write pointer reaches read pointer after write
CHECK(buf.GetWritePointer() == 3);
CHECK(buf[2] == 10);
// write backwards
buf.SetReadPointer(2);
CHECK(buf.WriteBck(15) == false); // write pointer reaches read pointer after write
CHECK(buf[3] == 15);
CHECK(buf.GetWritePointer() == 2);
}
}
TEST_CASE( "Circular Buffer practical test", "[CircularBufferTestPractical]" )
{
std::cout.precision(16);
SECTION( "Moving average" ) {
constexpr size_t Npts = 10;
std::vector<int> x(Npts);
for(size_t i = 0 ; i < Npts ; i++)
x[i] = rand() % 100;
PRINT_VEC(x);
// Compute moving average with absolute coordinates
constexpr size_t N = 3;
std::vector<int> xAvg(Npts-N);
for(size_t i = 0 ; i < Npts-N ; i++)
xAvg[i] = (x[i] + x[i+1] + x[i+2]) / 3;
PRINT_VEC(xAvg);
// Compute moving average with circular buffer
std::vector<int> xAvgBuf(Npts-N);
CircularBuffer<N,int> buf;
// Initialise the buffer
buf[0] = x[0];
buf[1] = x[1];
buf.SetWritePointer(2);
for(size_t i = 0 ; i < Npts-N ; i++)
{
buf.WriteFwd(x[i+2]);
xAvgBuf[i] = (buf[0] + buf[1] + buf[2]) / 3;
}
PRINT_VEC(xAvgBuf);
for(size_t i = 0 ; i < Npts-N ; i++)
CHECK(xAvg[i] == xAvgBuf[i]);
}
}