Skip to main content

simple_bitset/
bitset64.rs

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