diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96fcf03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Visual Studio +.vs/ +*.sln +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.suo +*.user +Debug/ +Release/ +x64/ +ipch/ +*.ilk +*.pdb +*.obj +*.log \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..06f649b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.14) +project(BitArray LANGUAGES CXX) + +# Требуем C++17 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Автоматическая загрузка Google Test +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip +) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +# Исходники BitArray +set(BITARRAY_SOURCES bitarray.cpp bitarray.h) + +# Статическая библиотека +add_library(bitarray STATIC ${BITARRAY_SOURCES}) +target_include_directories(bitarray PUBLIC .) + +# Демо-программа +add_executable(main main.cpp) +target_link_libraries(main bitarray) + +# Юнит-тесты +add_executable(bitarray_tests bitarray_tests.cpp) +target_link_libraries(bitarray_tests bitarray GTest::gtest GTest::gtest_main) + +# Поддержка CTest +enable_testing() +add_test(NAME BitArrayTests COMMAND bitarray_tests) diff --git a/bitarray.cpp b/bitarray.cpp new file mode 100644 index 0000000..395c716 --- /dev/null +++ b/bitarray.cpp @@ -0,0 +1,409 @@ +#include "bitarray.h" +#include +#include +#include +#include + +const int BITS_PER_WORD = sizeof(unsigned long) * CHAR_BIT; + +BitArray::BitArray() + : data(nullptr), num_bits(0), capacity(0) +{ +} + +BitArray::~BitArray() +{ + deallocate(); +} + +BitArray::BitArray(int num_bits, unsigned long value) + : data(nullptr), num_bits(0), capacity(0) +{ + if (num_bits < 0) + throw std::invalid_argument("num_bits must be non-negative"); + allocate(num_bits); + if (num_bits > 0) { + int init_bits = (num_bits < BITS_PER_WORD) ? num_bits : BITS_PER_WORD; + unsigned long mask = (init_bits == BITS_PER_WORD) ? ~0UL : ((1UL << init_bits) - 1); + data[0] = value & mask; + } +} + +BitArray::BitArray(const BitArray& b) + : data(nullptr), num_bits(0), capacity(0) +{ + allocate(b.num_bits); + if (b.num_bits > 0) { + std::memcpy(data, b.data, words_needed(b.num_bits) * sizeof(unsigned long)); + } +} + +void BitArray::swap(BitArray& b) +{ + unsigned long* tmp_data = data; + int tmp_bits = num_bits; + int tmp_cap = capacity; + + data = b.data; + num_bits = b.num_bits; + capacity = b.capacity; + + b.data = tmp_data; + b.num_bits = tmp_bits; + b.capacity = tmp_cap; +} + +BitArray& BitArray::operator=(const BitArray& b) +{ + if (this != &b) { + BitArray tmp(b); + swap(tmp); + } + return *this; +} + +void BitArray::allocate(int bits) +{ + if (bits < 0) + throw std::invalid_argument("bits must be non-negative"); + int words = words_needed(bits); + data = new unsigned long[words](); + num_bits = bits; + capacity = words * BITS_PER_WORD; +} + +void BitArray::deallocate() +{ + delete[] data; + data = nullptr; + num_bits = 0; + capacity = 0; +} + +int BitArray::words_needed(int bits) const +{ + return (bits + BITS_PER_WORD - 1) / BITS_PER_WORD; +} + +void BitArray::resize(int num_bits, bool value) +{ + if (num_bits < 0) + throw std::invalid_argument("num_bits must be non-negative"); + if (num_bits == this->num_bits) + return; + + // + int old_size = this->num_bits; + unsigned long* old_data = data; + int old_words = words_needed(old_size); + + // ( ) + allocate(num_bits); + + // + if (old_size > 0) { + int words_to_copy = std::min(old_words, words_needed(num_bits)); + std::memcpy(data, old_data, words_to_copy * sizeof(unsigned long)); + // "" + if (num_bits < old_size) { + int excess = capacity - num_bits; + if (excess > 0) { + unsigned long mask = (1UL << (BITS_PER_WORD - (excess % BITS_PER_WORD))) - 1; + if (excess % BITS_PER_WORD == 0) mask = ~0UL; + data[words_needed(num_bits) - 1] &= mask; + } + } + } + + // 1 + if (value && num_bits > old_size) { + for (int i = old_size; i < num_bits; ++i) { + set(i, true); + } + } + + delete[] old_data; +} + +void BitArray::clear() +{ + deallocate(); +} + +void BitArray::push_back(bool bit) +{ + resize(num_bits + 1, false); + if (bit) + set(num_bits - 1, true); +} + +BitArray& BitArray::operator&=(const BitArray& b) +{ + if (num_bits != b.num_bits) + throw std::invalid_argument("BitArray sizes must match for bitwise operations"); + int words = words_needed(num_bits); + for (int i = 0; i < words; ++i) + data[i] &= b.data[i]; + return *this; +} + +BitArray& BitArray::operator|=(const BitArray& b) +{ + if (num_bits != b.num_bits) + throw std::invalid_argument("BitArray sizes must match for bitwise operations"); + int words = words_needed(num_bits); + for (int i = 0; i < words; ++i) + data[i] |= b.data[i]; + return *this; +} + +BitArray& BitArray::operator^=(const BitArray& b) +{ + if (num_bits != b.num_bits) + throw std::invalid_argument("BitArray sizes must match for bitwise operations"); + int words = words_needed(num_bits); + for (int i = 0; i < words; ++i) + data[i] ^= b.data[i]; + return *this; +} + +BitArray& BitArray::operator<<=(int n) +{ + if (n < 0) return *this; + if (n >= num_bits) { + std::memset(data, 0, words_needed(num_bits) * sizeof(unsigned long)); + return *this; + } + if (n == 0) return *this; + + int word_shift = n / BITS_PER_WORD; + int bit_shift = n % BITS_PER_WORD; + + int total_words = words_needed(num_bits); + // Shift words + for (int i = total_words - 1; i >= word_shift; --i) { + data[i] = data[i - word_shift] << bit_shift; + if (bit_shift != 0 && i - word_shift - 1 >= 0) + data[i] |= data[i - word_shift - 1] >> (BITS_PER_WORD - bit_shift); + } + // Zero out lower words + for (int i = 0; i < word_shift; ++i) + data[i] = 0; + + // Clear bits beyond size + int excess = total_words * BITS_PER_WORD - num_bits; + if (excess > 0) { + unsigned long mask = (1UL << (BITS_PER_WORD - excess)) - 1; + data[total_words - 1] &= mask; + } + + return *this; +} + +BitArray& BitArray::operator>>=(int n) +{ + if (n < 0) return *this; + if (n >= num_bits) { + std::memset(data, 0, words_needed(num_bits) * sizeof(unsigned long)); + return *this; + } + if (n == 0) return *this; + + int word_shift = n / BITS_PER_WORD; + int bit_shift = n % BITS_PER_WORD; + + int total_words = words_needed(num_bits); + // Shift words + for (int i = 0; i < total_words - word_shift; ++i) { + data[i] = data[i + word_shift] >> bit_shift; + if (bit_shift != 0 && i + word_shift + 1 < total_words) + data[i] |= data[i + word_shift + 1] << (BITS_PER_WORD - bit_shift); + } + // Zero out upper words + for (int i = total_words - word_shift; i < total_words; ++i) + data[i] = 0; + + return *this; +} + +BitArray BitArray::operator<<(int n) const +{ + BitArray tmp(*this); + tmp <<= n; + return tmp; +} + +BitArray BitArray::operator>>(int n) const +{ + BitArray tmp(*this); + tmp >>= n; + return tmp; +} + +BitArray& BitArray::set(int n, bool val) +{ + if (n < 0 || n >= num_bits) + throw std::out_of_range("Bit index out of range"); + int word = n / BITS_PER_WORD; + int bit = n % BITS_PER_WORD; + if (val) + data[word] |= (1UL << bit); + else + data[word] &= ~(1UL << bit); + return *this; +} + +BitArray& BitArray::set() +{ + std::memset(data, 0xFF, words_needed(num_bits) * sizeof(unsigned long)); + // Clear excess bits + int excess = capacity - num_bits; + if (excess > 0) { + unsigned long mask = (1UL << (BITS_PER_WORD - excess % BITS_PER_WORD)) - 1; + if (excess % BITS_PER_WORD == 0) + mask = ~0UL; + else + mask = (1UL << (BITS_PER_WORD - (excess % BITS_PER_WORD))) - 1; + data[words_needed(num_bits) - 1] &= mask; + } + return *this; +} + +BitArray& BitArray::reset(int n) +{ + return set(n, false); +} + +BitArray& BitArray::reset() +{ + std::memset(data, 0, words_needed(num_bits) * sizeof(unsigned long)); + return *this; +} + +bool BitArray::any() const +{ + int words = words_needed(num_bits); + for (int i = 0; i < words; ++i) + if (data[i] != 0) + return true; + return false; +} + +bool BitArray::none() const +{ + return !any(); +} + +BitArray BitArray::operator~() const +{ + BitArray tmp(*this); + int words = words_needed(num_bits); + for (int i = 0; i < words; ++i) + tmp.data[i] = ~data[i]; + // Clear excess bits + int excess = tmp.capacity - tmp.num_bits; + if (excess > 0) { + int last_word = words - 1; + unsigned long mask = (1UL << (BITS_PER_WORD - (excess % BITS_PER_WORD))) - 1; + if (excess % BITS_PER_WORD == 0) + mask = ~0UL; + else + mask = (1UL << (BITS_PER_WORD - (excess % BITS_PER_WORD))) - 1; + tmp.data[last_word] &= mask; + } + return tmp; +} + +int BitArray::count() const { + if (num_bits == 0) return 0; + + int cnt = 0; + int words = words_needed(num_bits); + int full_words = num_bits / BITS_PER_WORD; + int last_bits = num_bits % BITS_PER_WORD; + + for (int i = 0; i < full_words; ++i) { + unsigned long w = data[i]; + while (w) { + cnt += w & 1; + w >>= 1; + } + } + + if (last_bits > 0) { + unsigned long w = data[full_words]; + for (int i = 0; i < last_bits; ++i) { + cnt += (w >> i) & 1; + } + } + + return cnt; +} + +bool BitArray::operator[](int i) const +{ + if (i < 0 || i >= num_bits) + throw std::out_of_range("Bit index out of range"); + int word = i / BITS_PER_WORD; + int bit = i % BITS_PER_WORD; + return (data[word] >> bit) & 1; +} + +int BitArray::size() const +{ + return num_bits; +} + +bool BitArray::empty() const +{ + return num_bits == 0; +} + +std::string BitArray::to_string() const +{ + std::string s; + s.reserve(num_bits); + for (int i = num_bits - 1; i >= 0; --i) { + s += ((*this)[i] ? '1' : '0'); + } + return s; +} + +// Free functions + +bool operator==(const BitArray& a, const BitArray& b) +{ + if (a.size() != b.size()) + return false; + int words = a.words_needed(a.size()); + for (int i = 0; i < words; ++i) + if (a.data[i] != b.data[i]) + return false; + return true; +} + +bool operator!=(const BitArray& a, const BitArray& b) +{ + return !(a == b); +} + +BitArray operator&(const BitArray& b1, const BitArray& b2) +{ + BitArray tmp(b1); + tmp &= b2; + return tmp; +} + +BitArray operator|(const BitArray& b1, const BitArray& b2) +{ + BitArray tmp(b1); + tmp |= b2; + return tmp; +} + +BitArray operator^(const BitArray& b1, const BitArray& b2) +{ + BitArray tmp(b1); + tmp ^= b2; + return tmp; +} \ No newline at end of file diff --git a/bitarray.h b/bitarray.h new file mode 100644 index 0000000..24c8ba1 --- /dev/null +++ b/bitarray.h @@ -0,0 +1,64 @@ +#ifndef BIT_ARRAY_H +#define BIT_ARRAY_H + +#include + +class BitArray +{ +public: + BitArray(); + ~BitArray(); + + explicit BitArray(int num_bits, unsigned long value = 0); + BitArray(const BitArray& b); + + void swap(BitArray& b); + BitArray& operator=(const BitArray& b); + + void resize(int num_bits, bool value = false); + void clear(); + void push_back(bool bit); + + BitArray& operator&=(const BitArray& b); + BitArray& operator|=(const BitArray& b); + BitArray& operator^=(const BitArray& b); + + BitArray& operator<<=(int n); + BitArray& operator>>=(int n); + BitArray operator<<(int n) const; + BitArray operator>>(int n) const; + + BitArray& set(int n, bool val = true); + BitArray& set(); + BitArray& reset(int n); + BitArray& reset(); + + bool any() const; + bool none() const; + BitArray operator~() const; + int count() const; + + bool operator[](int i) const; + + int size() const; + bool empty() const; + + std::string to_string() const; + +private: + unsigned long* data; + int num_bits; + int capacity; // in bits + + void allocate(int bits); + void deallocate(); + int words_needed(int bits) const; + + friend bool operator==(const BitArray& a, const BitArray& b); + friend bool operator!=(const BitArray& a, const BitArray& b); +}; +BitArray operator&(const BitArray& b1, const BitArray& b2); +BitArray operator|(const BitArray& b1, const BitArray& b2); +BitArray operator^(const BitArray& b1, const BitArray& b2); + +#endif // BIT_ARRAY_H \ No newline at end of file diff --git a/bitarray_tests.cpp b/bitarray_tests.cpp new file mode 100644 index 0000000..1d76b4d --- /dev/null +++ b/bitarray_tests.cpp @@ -0,0 +1,251 @@ +#include "bitarray.h" +#include +#include + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, DefaultConstructor) { + BitArray ba; + EXPECT_EQ(ba.size(), 0); + EXPECT_TRUE(ba.empty()); +} + +TEST(BitArrayTest, ConstructorWithValue) { + BitArray ba(5, 0b10110UL); + EXPECT_EQ(ba.size(), 5); + EXPECT_EQ(ba[0], false); + EXPECT_EQ(ba[1], true); + EXPECT_EQ(ba[2], true); + EXPECT_EQ(ba[3], false); + EXPECT_EQ(ba[4], true); +} + +TEST(BitArrayTest, ConstructorZeroSize) { + BitArray ba(0); + EXPECT_TRUE(ba.empty()); +} + +TEST(BitArrayTest, ConstructorNegativeThrows) { + EXPECT_THROW(BitArray(-1), std::invalid_argument); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, CopyConstructor) { + BitArray ba(6, 0b110011); + BitArray ba2(ba); + EXPECT_EQ(ba2, ba); +} + +TEST(BitArrayTest, AssignmentOperator) { + BitArray ba(4, 0b1101); + BitArray ba2; + ba2 = ba; + EXPECT_EQ(ba2, ba); +} + +TEST(BitArrayTest, SelfAssignment) { + BitArray ba(3, 0b101); + ba = ba; + EXPECT_EQ(ba.to_string(), "101"); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, ResizeGrowWithFalse) { + BitArray ba(2, 0b11); + ba.resize(5, false); + EXPECT_EQ(ba.to_string(), "00011"); +} + +TEST(BitArrayTest, ResizeGrowWithTrue) { + BitArray ba(2, 0b01); + ba.resize(5, true); + EXPECT_EQ(ba.to_string(), "11101"); +} + +TEST(BitArrayTest, ResizeShrink) { + BitArray ba(6, 0b111100); + ba.resize(3); + EXPECT_EQ(ba.to_string(), "100"); // only lowest 3 bits +} + +TEST(BitArrayTest, Clear) { + BitArray ba(5, 0b11111); + ba.clear(); + EXPECT_TRUE(ba.empty()); +} + +TEST(BitArrayTest, PushBack) { + BitArray ba; + ba.push_back(true); + ba.push_back(false); + ba.push_back(true); + EXPECT_EQ(ba.to_string(), "101"); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, SetSingleBit) { + BitArray ba(4); + ba.set(2, true); + EXPECT_EQ(ba.to_string(), "0100"); + ba.set(2, false); + EXPECT_EQ(ba.to_string(), "0000"); +} + +TEST(BitArrayTest, SetAllBits) { + BitArray ba(5); + ba.set(); + EXPECT_EQ(ba.to_string(), "11111"); +} + +TEST(BitArrayTest, ResetAllBits) { + BitArray ba(4, 0b1111); + ba.reset(); + EXPECT_EQ(ba.to_string(), "0000"); +} + +TEST(BitArrayTest, ResetSingleBit) { + BitArray ba(3, 0b111); + ba.reset(1); + EXPECT_EQ(ba.to_string(), "101"); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, BitwiseAND) { + BitArray a(4, 0b1100); + BitArray b(4, 0b1010); + BitArray c = a & b; + EXPECT_EQ(c.to_string(), "1000"); + a &= b; + EXPECT_EQ(a.to_string(), "1000"); +} + +TEST(BitArrayTest, BitwiseOR) { + BitArray a(3, 0b101); + BitArray b(3, 0b011); + EXPECT_EQ((a | b).to_string(), "111"); + a |= b; + EXPECT_EQ(a.to_string(), "111"); +} + +TEST(BitArrayTest, BitwiseXOR) { + BitArray a(4, 0b1100); + BitArray b(4, 0b1010); + EXPECT_EQ((a ^ b).to_string(), "0110"); +} + +TEST(BitArrayTest, NOT) { + BitArray a(4, 0b1010); + BitArray b = ~a; + EXPECT_EQ(b.to_string(), "0101"); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, LeftShift) { + BitArray a(5, 0b10011); + BitArray b = a << 2; + EXPECT_EQ(b.to_string(), "01100"); + a <<= 2; + EXPECT_EQ(a.to_string(), "01100"); +} + +TEST(BitArrayTest, RightShift) { + BitArray a(5, 0b10011); + BitArray b = a >> 2; + EXPECT_EQ(b.to_string(), "00100"); + a >>= 2; + EXPECT_EQ(a.to_string(), "00100"); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, AnyAndNone) { + BitArray a(3); + EXPECT_FALSE(a.any()); + EXPECT_TRUE(a.none()); + + a.set(1); + EXPECT_TRUE(a.any()); + EXPECT_FALSE(a.none()); +} + +TEST(BitArrayTest, Count) { + BitArray a(6, 0b101101); + EXPECT_EQ(a.count(), 4); +} + +TEST(BitArrayTest, OperatorBracket) { + BitArray a(4, 0b1101); + EXPECT_EQ(a[0], true); + EXPECT_EQ(a[1], false); + EXPECT_EQ(a[2], true); + EXPECT_EQ(a[3], true); +} + +TEST(BitArrayTest, SizeAndEmpty) { + BitArray a; + EXPECT_EQ(a.size(), 0); + EXPECT_TRUE(a.empty()); + + a.resize(10); + EXPECT_EQ(a.size(), 10); + EXPECT_FALSE(a.empty()); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, ToString) { + BitArray a(5, 0b11001); + EXPECT_EQ(a.to_string(), "11001"); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, Equality) { + BitArray a(4, 0b1010); + BitArray b(4, 0b1010); + BitArray c(4, 0b0101); + EXPECT_TRUE(a == b); + EXPECT_FALSE(a == c); + EXPECT_TRUE(a != c); +} + +// -------------------------- +// +// -------------------------- + +TEST(BitArrayTest, OutOfRangeAccessThrows) { + BitArray a(3); + EXPECT_THROW(a[3], std::out_of_range); + EXPECT_THROW(a[-1], std::out_of_range); +} + +TEST(BitArrayTest, BitwiseMismatchSizeThrows) { + BitArray a(3); + BitArray b(4); + EXPECT_THROW(a &= b, std::invalid_argument); + EXPECT_THROW(a |= b, std::invalid_argument); + EXPECT_THROW(a ^= b, std::invalid_argument); +} \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..b2392a7 --- /dev/null +++ b/main.cpp @@ -0,0 +1,24 @@ +#include "bitarray.h" +#include + +int main() { + BitArray a(10, 0b1010101010UL); + std::cout << "a: " << a.to_string() << " (size=" << a.size() << ")\n"; + + BitArray b = a; + b <<= 2; + std::cout << "a << 2: " << b.to_string() << "\n"; + + BitArray c(10); + c.set(0).set(2).set(4); + std::cout << "c: " << c.to_string() << "\n"; + + BitArray d = a & c; + std::cout << "a & c: " << d.to_string() << "\n"; + + std::cout << "count(a): " << a.count() << "\n"; + std::cout << "any(c): " << c.any() << "\n"; + std::cout << "none(empty): " << BitArray().none() << "\n"; + + return 0; +} \ No newline at end of file