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