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