Skip to main content

simple_bitset/
bitset64.rs

1use core::fmt;
2use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index};
3use serde::{Deserialize, Serialize};
4
5/// A memory-efficient 64-bit set for embedded environments.
6/// Note that it data is a singlet: this makes comparison with `BitSet128` duplet clearer.
7#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]
8pub struct BitSet64(u64);
9
10impl BitSet64 {
11    /// Create a new empty bitset.
12    pub const fn new() -> Self {
13        Self(0)
14    }
15}
16
17impl Default for BitSet64 {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl BitSet64 {
24    /// Resets all bits to 0.
25    #[inline]
26    pub fn reset_all(&mut self) {
27        self.0 = 0;
28    }
29
30    /// Resets the bit at `index` to 0. Does nothing if the index is out of bounds.
31    #[inline]
32    pub fn reset(&mut self, index: u8) {
33        if index < 64 {
34            self.0 &= !(1u64 << index);
35        }
36    }
37
38    /// Sets all bits to 1.
39    #[inline]
40    pub fn set_all(&mut self) {
41        self.0 = u64::MAX;
42    }
43
44    /// Sets the bit at `index` to 1. Does nothing if the index is out of bounds.
45    #[inline]
46    pub fn set(&mut self, index: u8) {
47        if index < 64 {
48            self.0 |= 1u64 << index;
49        }
50    }
51
52    /// Flips the bit at `index`. Does nothing if the index is out of bounds.
53    #[inline]
54    pub fn flip(&mut self, index: u8) {
55        if index < 64 {
56            self.0 ^= 1u64 << index;
57        }
58    }
59
60    /// Flips all bits in the bitset (0s become 1s, and 1s become 0s).
61    #[inline]
62    pub fn flip_all(&mut self) {
63        self.0 = !self.0;
64    }
65
66    /// In-place Difference / Mask-Clear. Clears any bits that are set in `other`.
67    /// This represents the mathematical operation: `self = self AND NOT other`.
68    #[inline]
69    pub fn and_not(&mut self, other: Self) {
70        self.0 &= !other.0;
71    }
72
73    /// Tests if the bit at `index` is 1.
74    /// Returns false if the bit is 0 or index is out of bounds.
75    #[inline]
76    pub fn test(&self, index: u8) -> bool {
77        if index < 64 {
78            (self.0 & (1u64 << index)) != 0
79        } else {
80            false
81        }
82    }
83
84    /// Returns the number of set bits (population count).
85    #[inline]
86    pub const fn count_ones(&self) -> u32 {
87        self.0.count_ones()
88    }
89
90    /// Returns the number of leading zeros in the bitset,
91    /// counting from the most significant bit (index 63).
92    #[inline]
93    pub const fn leading_zeros(&self) -> u32 {
94        self.0.leading_zeros()
95    }
96
97    /// Returns true if no bits are set.
98    #[inline]
99    pub const fn is_empty(&self) -> bool {
100        self.0 == 0
101    }
102
103    /// Returns the highest index set, or None if the bitset is empty.
104    /// Useful for finding the "top" of a priority queue or resource map.
105    #[inline]
106    pub const fn last_set(&self) -> Option<u8> {
107        if self.is_empty() {
108            None
109        } else {
110            // Cast is safe as (63 - leading) is guaranteed to be between 0 and 63.
111            #[allow(clippy::cast_possible_truncation)]
112            Some(63 - self.0.leading_zeros() as u8)
113        }
114    }
115
116    /// Returns true if this bitset contains all the bits set in `other`.
117    #[inline]
118    pub const fn is_superset(&self, other: &BitSet64) -> bool {
119        // A bitset is a superset if clearing any bits not present in self
120        // results in exactly the other bitset configuration for both halves.
121        (self.0 & other.0) == other.0
122    }
123
124    /// Returns true if this bitset is a subset of `other`.
125    #[inline]
126    pub const fn is_subset(&self, other: &BitSet64) -> bool {
127        other.is_superset(self)
128    }
129
130    /// Returns true if this bitset shares at least one common set bit with `other`.
131    /// Returns false if there is no overlap or if either bitset is empty.
132    #[inline]
133    pub const fn intersects(&self, other: &Self) -> bool {
134        // Run a bitwise AND between bitsets.
135        // If the result is non-zero, an intersection exists.
136        self.0 & other.0 != 0
137    }
138
139    /// Returns an iterator over the indices of the set bits.
140    #[inline]
141    pub fn iter(&self) -> BitSet64Iter {
142        self.into_iter()
143    }
144}
145
146// **** Bit operations ****
147
148impl BitOr for BitSet64 {
149    type Output = Self;
150    fn bitor(self, rhs: Self) -> Self::Output {
151        Self(self.0 | rhs.0)
152    }
153}
154
155impl BitAnd for BitSet64 {
156    type Output = Self;
157    fn bitand(self, rhs: Self) -> Self::Output {
158        Self(self.0 & rhs.0)
159    }
160}
161
162impl BitXor for BitSet64 {
163    type Output = Self;
164    fn bitxor(self, rhs: Self) -> Self::Output {
165        Self(self.0 ^ rhs.0)
166    }
167}
168
169impl BitOrAssign for BitSet64 {
170    fn bitor_assign(&mut self, rhs: Self) {
171        self.0 |= rhs.0;
172    }
173}
174
175impl BitAndAssign for BitSet64 {
176    fn bitand_assign(&mut self, rhs: Self) {
177        self.0 &= rhs.0;
178    }
179}
180
181impl BitXorAssign for BitSet64 {
182    fn bitxor_assign(&mut self, rhs: Self) {
183        self.0 ^= rhs.0;
184    }
185}
186
187impl Index<u8> for BitSet64 {
188    type Output = bool;
189
190    fn index(&self, index: u8) -> &Self::Output {
191        // We use static booleans because we must return a reference
192        if self.test(index) { &true } else { &false }
193    }
194}
195
196impl Index<usize> for BitSet64 {
197    type Output = bool;
198
199    #[allow(clippy::cast_possible_truncation)]
200    fn index(&self, index: usize) -> &Self::Output {
201        // We use static booleans because we must return a reference
202        if self.test(index as u8) { &true } else { &false }
203    }
204}
205
206/// `BitSet64` from `u32`.
207impl From<u32> for BitSet64 {
208    #[inline]
209    fn from(a: u32) -> Self {
210        Self(u64::from(a))
211    }
212}
213
214/// `BitSet64` from `(u32,u32)`.
215impl From<(u32, u32)> for BitSet64 {
216    #[inline]
217    fn from((a, b): (u32, u32)) -> Self {
218        Self(u64::from(a) << 32 | u64::from(b))
219    }
220}
221
222// **** Iter ****
223
224#[derive(Debug, Default, Eq, PartialEq)]
225pub struct BitSet64Iter(u64);
226
227/// Consuming iterator.
228impl Iterator for BitSet64Iter {
229    type Item = u8;
230
231    fn next(&mut self) -> Option<Self::Item> {
232        if self.0 == 0 {
233            None
234        } else {
235            // Find the index of the least significant bit set to 1
236            #[allow(clippy::cast_possible_truncation)]
237            let index = self.0.trailing_zeros() as u8;
238            // Clear the least significant bit to prep for next iteration
239            self.0 &= self.0 - 1;
240            Some(index)
241        }
242    }
243
244    #[inline]
245    fn size_hint(&self) -> (usize, Option<usize>) {
246        // We know exactly how many bits are left to yield at any moment!
247        let len = self.0.count_ones() as usize;
248        (len, Some(len))
249    }
250}
251
252// Implementing ExactSizeIterator unlocks additional optimizations automatically
253impl ExactSizeIterator for BitSet64Iter {}
254impl core::iter::FusedIterator for BitSet64Iter {}
255
256/// Non-consuming iterator for bitset reference.
257impl IntoIterator for &BitSet64 {
258    type Item = u8;
259    type IntoIter = BitSet64Iter;
260
261    fn into_iter(self) -> Self::IntoIter {
262        // Since `BitSet64` is `Copy` and just a `(u64)`,
263        // we just pass the underlying value to the iterator.
264        BitSet64Iter(self.0)
265    }
266}
267
268/// Non-consuming iterator for bitset.
269impl IntoIterator for BitSet64 {
270    type Item = u8;
271    type IntoIter = BitSet64Iter;
272
273    fn into_iter(self) -> Self::IntoIter {
274        // Since `BitSet64` is `Copy` and just a `(u64)`,
275        // we just pass the underlying value to the iterator.
276        BitSet64Iter(self.0)
277    }
278}
279
280/// From iterator for bitset.
281impl FromIterator<u8> for BitSet64 {
282    fn from_iter<I: IntoIterator<Item = u8>>(iter: I) -> Self {
283        let mut bitset = Self::new();
284        for index in iter {
285            bitset.set(index);
286        }
287        bitset
288    }
289}
290
291impl Extend<u8> for BitSet64 {
292    fn extend<I: IntoIterator<Item = u8>>(&mut self, iter: I) {
293        for index in iter {
294            self.set(index);
295        }
296    }
297}
298
299// **** fmt ****
300
301impl fmt::Binary for BitSet64 {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        // Handle the "0b" prefix if requested via {:#b}
304        if f.alternate() {
305            f.write_str("0b")?;
306        }
307        // Print from high bits to low bits (left-to-right)
308        for i in (0..64).rev() {
309            let val = (self.0 >> i) & 1;
310            write!(f, "{val}")?;
311        }
312
313        Ok(())
314    }
315}
316
317impl fmt::UpperHex for BitSet64 {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        if f.alternate() {
320            f.write_str("0x")?;
321        }
322        write!(f, "{:016X}", self.0)
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    fn is_normal<T: Sized + Send + Sync + Unpin>() {}
331    fn _is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
332    fn is_config<
333        T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq + Serialize + for<'a> Deserialize<'a>,
334    >() {
335    }
336
337    #[test]
338    fn normal_types() {
339        is_config::<BitSet64>();
340        is_normal::<BitSet64Iter>();
341    }
342    #[test]
343    fn new() {
344        let mut bits = BitSet64::new();
345        bits.set(42);
346        assert!(bits[42u8]);
347        assert!(bits.test(42));
348    }
349    #[allow(unused)]
350    #[test]
351    fn const_new() {
352        const FLAGS: BitSet64 = BitSet64::new();
353        const EMPTY_CHECK: bool = FLAGS.is_empty(); // Evaluated at compile time
354    }
355    #[test]
356    fn assign() {
357        let mut bits = BitSet64::new();
358        bits.set(42);
359        assert!(bits[42u8]);
360        assert!(bits.test(42));
361        let mask = bits;
362        assert!(mask.test(42));
363    }
364    #[test]
365    fn from() {
366        let _bits = BitSet64::from((0xab_u32, 0x12_u32));
367    }
368    #[test]
369    fn flip_all() {
370        let mut bitset = BitSet64::new();
371
372        // Alternating pattern test
373        bitset.set(0);
374        bitset.set(32);
375
376        bitset.flip_all();
377
378        // Bit 0 and 64 should now be 0, others should be 1
379        assert!(!bitset.test(0));
380        assert!(!bitset.test(32));
381        assert!(bitset.test(1));
382        assert!(bitset.test(33));
383
384        // Full cycle test (Empty -> Full -> Empty)
385        let mut empty_set = BitSet64::new();
386        empty_set.flip_all(); // Should become full
387
388        let mut full_set = BitSet64::new();
389        full_set.set_all();
390
391        assert_eq!(empty_set, full_set);
392
393        empty_set.flip_all(); // Should return to completely empty
394        assert!(empty_set.is_empty());
395    }
396    #[test]
397    fn inplace_logical_ops() {
398        let mut set_a = BitSet64::new();
399        let mut set_b = BitSet64::new();
400
401        // Setup overlapping patterns crossing 64-bit boundaries
402        set_a.set(10);
403        set_a.set(50);
404
405        set_b.set(10);
406        set_b.set(60);
407
408        // Test BitAndAssign (&=)
409        let mut result = set_a;
410        result &= set_b;
411        assert!(result.test(10));
412        assert!(!result.test(50));
413        assert!(!result.test(60));
414
415        // Test BitOrAssign (|=)
416        let mut result = set_a;
417        result |= set_b;
418        assert!(result.test(10));
419        assert!(result.test(50));
420        assert!(result.test(60));
421
422        // Test BitXorAssign (^=)
423        let mut result = set_a;
424        result ^= set_b;
425        assert!(!result.test(10)); // Both were 1, turns to 0
426        assert!(result.test(50));
427        assert!(result.test(60));
428
429        // Test and_not
430        let mut result = set_a;
431        result.and_not(set_b);
432        assert!(!result.test(10)); // Cleared by mask
433        assert!(result.test(50)); // Preserved
434        assert!(!result.test(60)); // Never present in set_a
435    }
436    #[test]
437    fn exercise() {
438        let mut system_flags = BitSet64::new();
439        let error_mask = BitSet64::new(); // imagine this has error bits set
440
441        // Combine with OR-assign
442        system_flags |= error_mask;
443
444        // Toggle bits with XOR-assign
445        system_flags ^= error_mask;
446
447        // Mask out bits with AND-assign
448        system_flags &= BitSet64(0x0000_FFFF_FFFF_FFFF);
449
450        let mut set_a = BitSet64::new();
451        set_a.set(10);
452        set_a.set(20);
453
454        let mut set_b = BitSet64::new();
455        set_b.set(20);
456        set_b.set(30);
457
458        // Intersection (AND): only bit 20 remains
459        let common = set_a & set_b;
460        assert!(!common.test(10));
461        assert!(common.test(20));
462        assert!(!common.test(30));
463
464        // Union (OR): bits 10, 20, and 30 are set
465        let all = set_a | set_b;
466        assert!(all.test(10));
467        assert!(all.test(20));
468        assert!(all.test(30));
469
470        // Difference (XOR): bits 10 and 30 are set (20 is cancelled out)
471        let diff = set_a ^ set_b;
472        assert!(diff.test(10));
473        assert!(!diff.test(20));
474        assert!(diff.test(30));
475    }
476    #[test]
477    fn iterator_consuming() {
478        let mut bits = BitSet64::new();
479        bits.set(2);
480        bits.set(10);
481
482        let mut sum = 0;
483        let mut count = 0;
484        for bit in &bits {
485            sum += bit;
486            count += 1;
487        }
488        assert_eq!(2, count);
489        assert_eq!(12, sum);
490    }
491    #[test]
492    fn into_iter_consuming() {
493        let mut bits = BitSet64::new();
494        bits.set(0);
495        bits.set(10);
496        bits.set(63);
497
498        // Consuming iterator
499        let mut iter = bits.into_iter();
500
501        assert_eq!(iter.next(), Some(0));
502        assert_eq!(iter.next(), Some(10));
503        assert_eq!(iter.next(), Some(63));
504        assert_eq!(iter.next(), None);
505
506        // bits is moved here and can no longer be used
507    }
508
509    #[test]
510    fn non_consuming_iterator() {
511        let bits = BitSet64(0b1101); // Bits 0, 2, and 3 are set
512
513        let mut sum = 0;
514        let mut count = 0;
515
516        // Use reference to bits rather than bits.iter()
517        for bit in &bits {
518            sum += bit;
519            count += 1;
520        }
521        assert_eq!(3, count);
522        assert_eq!(5, sum);
523
524        let mut sum = 0;
525        let mut count = 0;
526        // Using a reference in a loop
527        for bit in &bits {
528            sum += bit;
529            count += 1;
530        }
531        assert_eq!(3, count);
532        assert_eq!(5, sum);
533    }
534
535    #[test]
536    fn non_consuming_iterator2() {
537        let mut bits = BitSet64::new();
538        bits.set(5);
539        bits.set(12);
540
541        // Test using the .iter() method
542        let count = bits.iter().count();
543        assert_eq!(count, 2);
544
545        // Test using & reference in a for-loop
546        let mut last_val = 0;
547        for bit in &bits {
548            last_val = bit;
549        }
550        assert_eq!(last_val, 12);
551
552        // test original bits is still valid
553        assert!(bits.test(5));
554    }
555
556    #[test]
557    fn from_iterator() {
558        let indices = [1, 3, 5];
559        // collect() uses FromIterator
560        let bits: BitSet64 = indices.iter().copied().collect();
561
562        assert!(bits.test(1));
563        assert!(bits.test(3));
564        assert!(bits.test(5));
565        assert!(!bits.test(2));
566    }
567
568    #[test]
569    fn empty_and_full() {
570        let empty = BitSet64::new();
571        assert_eq!(empty.iter().count(), 0);
572
573        let mut full = BitSet64::new();
574        full.set_all();
575        assert_eq!(full.iter().count(), 64);
576    }
577}