vecs
Fast, flexible ecs in C++ with ergonomic API
Loading...
Searching...
No Matches
dynamic_bitset.h
1#ifndef __VECS_STORAGE_DYNAMIC_BITSET_H__
2#define __VECS_STORAGE_DYNAMIC_BITSET_H__
3
4#include <cstdint>
5#include <limits>
6#include <string>
7#include <vector>
8
9#if defined(_MSC_VER)
10 #include <intrin.h>
11 #pragma intrinsic(_BitScanReverse64)
12#endif
13
14namespace vecs::storage {
15
20 public:
21 using size_type = std::uint64_t;
22 using block_type = std::uint64_t;
23 static constexpr size_type bits_per_block =
24 std::numeric_limits<block_type>::digits;
25
26 dynamic_bitset() = default;
27
28 // explicit dynamic_bitset(size_type num_bits) : m_num_bits(num_bits) {
29 // m_blocks.resize((num_bits + bits_per_block - 1) / bits_per_block);
30 // }
31
32 explicit dynamic_bitset(size_type value) {
33 if (value == 0) {
34 m_num_bits = 1;
35 m_blocks.resize(1, 0);
36 } else {
37 // Calculate number of bits needed
38 m_num_bits = std::numeric_limits<size_type>::digits - clzll(value);
39 m_blocks.resize((m_num_bits + bits_per_block - 1) / bits_per_block);
40 m_blocks[0] = value;
41 zero_unused_bits();
42 }
43 }
44
45 size_type size() const {
46 return m_num_bits;
47 }
48
49 void clear() {
50 std::fill(m_blocks.begin(), m_blocks.end(), 0);
51 }
52
53 void resize(size_type nbits) {
54 m_num_bits = nbits;
55 m_blocks.resize((nbits + bits_per_block - 1) / bits_per_block);
56 zero_unused_bits();
57 }
58
59 dynamic_bitset& masked_and_not(dynamic_bitset& exclude_mask) {
60 if (exclude_mask.size() > size()) {
61 resize(exclude_mask.size());
62 } else if (size() > exclude_mask.size()) {
63 exclude_mask.resize(size());
64 }
65
66 for (size_type i = 0; i < m_blocks.size(); ++i) {
67 m_blocks[i] &= ~exclude_mask.m_blocks[i];
68 }
69 zero_unused_bits();
70 return *this;
71 }
72
73 // Bitwise AND assignment
74 dynamic_bitset& operator&=(const dynamic_bitset& rhs) {
75 size_type min_blocks = std::min(m_blocks.size(), rhs.m_blocks.size());
76 for (size_type i = 0; i < min_blocks; ++i) {
77 m_blocks[i] &= rhs.m_blocks[i];
78 }
79 // Zero the remaining blocks if lhs is longer than rhs
80 if (m_blocks.size() > rhs.m_blocks.size()) {
81 std::fill(m_blocks.begin() + min_blocks, m_blocks.end(), 0);
82 }
83 zero_unused_bits();
84 return *this;
85 }
86
87 // Bitwise OR assignment
88 dynamic_bitset& operator|=(const dynamic_bitset& rhs) {
89 if (rhs.m_num_bits > m_num_bits) {
90 resize(rhs.m_num_bits);
91 }
92 for (size_type i = 0; i < rhs.m_blocks.size(); ++i) {
93 m_blocks[i] |= rhs.m_blocks[i];
94 }
95 zero_unused_bits();
96 return *this;
97 }
98
99 // Bitwise XOR assignment
100 dynamic_bitset& operator^=(const dynamic_bitset& rhs) {
101 if (rhs.m_num_bits > m_num_bits) {
102 resize(rhs.m_num_bits);
103 }
104 for (size_type i = 0; i < rhs.m_blocks.size(); ++i) {
105 m_blocks[i] ^= rhs.m_blocks[i];
106 }
107 zero_unused_bits();
108 return *this;
109 }
110
111 // Bitwise NOT
112 dynamic_bitset operator~() const {
113 dynamic_bitset result(*this);
114 for (auto& block : result.m_blocks) {
115 block = ~block;
116 }
117 result.zero_unused_bits();
118 return result;
119 }
120
121 // Left shift assignment
122 dynamic_bitset& operator<<=(size_type shift) {
123 if (shift >= m_num_bits) {
124 // All bits are shifted out
125 std::fill(m_blocks.begin(), m_blocks.end(), 0);
126 return *this;
127 }
128
129 size_type block_shift = shift / bits_per_block;
130 size_type bit_shift = shift % bits_per_block;
131 size_type inv_bit_shift = bits_per_block - bit_shift;
132
133 if (block_shift != 0) {
134 for (size_type i = m_blocks.size(); i-- > block_shift;) {
135 m_blocks[i] = m_blocks[i - block_shift];
136 }
137 std::fill(m_blocks.begin(), m_blocks.begin() + block_shift, 0);
138 }
139
140 if (bit_shift != 0) {
141 block_type carry = 0;
142 for (size_type i = block_shift; i < m_blocks.size(); ++i) {
143 block_type temp = m_blocks[i];
144 m_blocks[i] = (temp << bit_shift) | carry;
145 carry = temp >> inv_bit_shift;
146 }
147 }
148 zero_unused_bits();
149 return *this;
150 }
151
152 // Right shift assignment
153 dynamic_bitset& operator>>=(size_type shift) {
154 if (shift >= m_num_bits) {
155 // All bits are shifted out
156 std::fill(m_blocks.begin(), m_blocks.end(), 0);
157 return *this;
158 }
159
160 size_type block_shift = shift / bits_per_block;
161 size_type bit_shift = shift % bits_per_block;
162 size_type inv_bit_shift = bits_per_block - bit_shift;
163
164 if (block_shift != 0) {
165 for (size_type i = 0; i < m_blocks.size() - block_shift; ++i) {
166 m_blocks[i] = m_blocks[i + block_shift];
167 }
168 std::fill(m_blocks.end() - block_shift, m_blocks.end(), 0);
169 }
170
171 if (bit_shift != 0) {
172 block_type carry = 0;
173 for (size_type i = m_blocks.size(); i-- > 0;) {
174 block_type temp = m_blocks[i];
175 m_blocks[i] = (temp >> bit_shift) | carry;
176 carry = temp << inv_bit_shift;
177 }
178 }
179 zero_unused_bits();
180 return *this;
181 }
182
183 // Bitwise operators
184 friend dynamic_bitset
185 operator&(dynamic_bitset lhs, const dynamic_bitset& rhs) {
186 lhs &= rhs;
187 return lhs;
188 }
189
190 friend dynamic_bitset
191 operator|(dynamic_bitset lhs, const dynamic_bitset& rhs) {
192 lhs |= rhs;
193 return lhs;
194 }
195
196 friend dynamic_bitset
197 operator^(dynamic_bitset lhs, const dynamic_bitset& rhs) {
198 lhs ^= rhs;
199 return lhs;
200 }
201
202 friend dynamic_bitset operator<<(dynamic_bitset lhs, size_type shift) {
203 lhs <<= shift;
204 return lhs;
205 }
206
207 friend dynamic_bitset operator>>(dynamic_bitset lhs, size_type shift) {
208 lhs >>= shift;
209 return lhs;
210 }
211
212 // Access operators
213 bool operator[](size_type pos) const {
214 if (pos >= m_num_bits) {
215 return false;
216 }
217 size_type block_index = pos / bits_per_block;
218 size_type bit_index = pos % bits_per_block;
219 return (m_blocks[block_index] & (block_type(1) << bit_index)) != 0;
220 }
221
222 // Set bit at position
223 void set(size_type pos, bool value = true) {
224 if (pos >= m_num_bits) {
225 resize(pos + 1);
226 }
227 size_type block_index = pos / bits_per_block;
228 size_type bit_index = pos % bits_per_block;
229 if (value) {
230 m_blocks[block_index] |= (block_type(1) << bit_index);
231 } else {
232 m_blocks[block_index] &= ~(block_type(1) << bit_index);
233 }
234 }
235
236 // Reset bit at position
237 void reset(size_type pos) {
238 set(pos, false);
239 }
240
241 // Flip bit at position
242 void flip(size_type pos) {
243 if (pos >= m_num_bits) {
244 resize(pos + 1);
245 }
246 size_type block_index = pos / bits_per_block;
247 size_type bit_index = pos % bits_per_block;
248 m_blocks[block_index] ^= (block_type(1) << bit_index);
249 }
250
251 // Comparison operators
252 bool operator==(const dynamic_bitset& rhs) const {
253 if (m_num_bits != rhs.m_num_bits) {
254 return false;
255 }
256 return m_blocks == rhs.m_blocks;
257 }
258
259 bool operator!=(const dynamic_bitset& rhs) const {
260 return !(*this == rhs);
261 }
262
263 // Count number of set bits
264 size_type count() const {
265 size_type cnt = 0;
266 for (const auto& block : m_blocks) {
267 cnt += popcount(block);
268 }
269 return cnt;
270 }
271
272 // Check if any bits are set
273 bool any() const {
274 for (const auto& m_block : m_blocks) {
275 if (m_block != 0) {
276 return true;
277 }
278 }
279 return false;
280 }
281
282 // Check if no bits are set
283 bool none() const {
284 return !any();
285 }
286
287 bool contains(const dynamic_bitset& other) const {
288 // Compare up to the size of the `other` mask
289 for (size_t i = 0; i < other.size(); ++i) {
290 if (other.test(i) && !this->test(i)) {
291 return false;
292 }
293 }
294 return true;
295 }
296
297 // Test bit at position
298 bool test(size_type pos) const {
299 return operator[](pos);
300 }
301
302 std::string to_string() const {
303 std::string result(m_num_bits, '0'); // Preallocate with '0's
304
305 for (size_type i = 0; i < m_num_bits; ++i) {
306 if (test(i)) {
307 result[m_num_bits - 1 - i] = '1'; // Set bit to '1' directly
308 }
309 }
310 return result;
311 }
312
313 private:
314 std::vector<block_type> m_blocks;
315 size_t m_num_bits {0};
316
317 void zero_unused_bits() {
318 if (m_num_bits == 0)
319 return;
320 size_type unused_bits = bits_per_block - (m_num_bits % bits_per_block);
321 if (unused_bits != bits_per_block) {
322 m_blocks.back() &= (block_type(~0) >> unused_bits);
323 }
324 }
325
326#if defined(_MSC_VER)
327 inline static int clzll(unsigned long long value) {
328 unsigned long index;
329 return _BitScanReverse64(&index, value) ? (63 - index) : 64;
330 }
331#else
332 inline static int clzll(unsigned long long value) {
333 return value == 0 ? 64 : __builtin_clzll(value);
334 }
335#endif
336
337 static size_type popcount(block_type x) {
338#if defined(__GNUC__) || defined(__clang__)
339 if constexpr (sizeof(block_type) <= sizeof(unsigned int))
340 return __builtin_popcount(x);
341 else if constexpr (sizeof(block_type) <= sizeof(unsigned long))
342 return __builtin_popcountl(x);
343 else if constexpr (sizeof(block_type) <= sizeof(unsigned long long))
344 return __builtin_popcountll(x);
345 else {
346 // Fallback for larger types
347 size_type count = 0;
348 while (x) {
349 x &= x - 1;
350 ++count;
351 }
352 return count;
353 }
354#elif defined(_MSC_VER)
355 if constexpr (sizeof(block_type) == sizeof(unsigned int))
356 return __popcnt(static_cast<unsigned int>(x));
357 else if constexpr (sizeof(block_type) == sizeof(unsigned long long))
358 return __popcnt64(static_cast<unsigned long long>(x));
359 else {
360 // Fallback for larger types
361 size_type count = 0;
362 while (x) {
363 x &= x - 1;
364 ++count;
365 }
366 return count;
367 }
368#else
369 // Portable fallback
370 size_type count = 0;
371 while (x) {
372 x &= x - 1;
373 ++count;
374 }
375 return count;
376#endif
377 }
378 friend struct std::hash<dynamic_bitset>;
379};
380
381} // namespace vecs::storage
382
383namespace std {
384
385template<>
386struct hash<vecs::storage::dynamic_bitset> {
387 std::size_t operator()(const vecs::storage::dynamic_bitset& bitset) const {
388 using size_type = typename vecs::storage::dynamic_bitset::size_type;
389 using block_type = typename vecs::storage::dynamic_bitset::block_type;
390
391 std::size_t hash_value = (sizeof(std::size_t) == 4)
392 ? 0x811c9dc5 // 32-bit FNV-1a hash seed
393 : 0xcbf29ce484222325; // 64-bit FNV-1a hash seed
394
395 for (const block_type& block : bitset.m_blocks) {
396 hash_value ^= std::hash<block_type> {}(block) + 0x9e3779b9
397 + (hash_value << 6) + (hash_value >> 2);
398 }
399
400 return hash_value;
401 }
402};
403
404} // namespace std
405
406#endif
A dynamic bitset that automatically grows to support an arbitrary number of bits.
Definition dynamic_bitset.h:19