Skip to main content

dodecet_encoder/
dodecet.rs

1//! # Dodecet: The 12-bit Building Block
2//!
3//! A dodecet is a 12-bit value composed of 3 nibbles (4-bit groups).
4//! It's the fundamental unit of the dodecet encoding system.
5
6use crate::{DodecetError, Result, MAX_DODECET, NIBBLES};
7
8/// A 12-bit dodecet value (0-4095)
9///
10/// # Example
11///
12/// ```rust
13/// use dodecet_encoder::Dodecet;
14///
15/// let d = Dodecet::from_hex(0xABC);
16/// assert_eq!(d.value(), 0xABC);
17/// assert_eq!(d.nibble(0).unwrap(), 0xC);
18/// assert_eq!(d.nibble(1).unwrap(), 0xB);
19/// assert_eq!(d.nibble(2).unwrap(), 0xA);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[derive(Default)]
23pub struct Dodecet {
24    value: u16,
25}
26
27impl Dodecet {
28    /// Create a new dodecet from a u16 value
29    ///
30    /// # Errors
31    /// Returns `DodecetError::Overflow` if value > 4095
32    ///
33    /// # Example
34    ///
35    /// ```rust
36    /// use dodecet_encoder::Dodecet;
37    ///
38    /// let d = Dodecet::new(0xABC).unwrap();
39    /// assert_eq!(d.value(), 0xABC);
40    /// ```
41    pub fn new(value: u16) -> Result<Self> {
42        if value > MAX_DODECET {
43            Err(DodecetError::Overflow)
44        } else {
45            Ok(Dodecet { value })
46        }
47    }
48
49    /// Create a dodecet from a hex value (unchecked)
50    ///
51    /// # Safety
52    /// Caller must ensure value <= 4095
53    ///
54    /// # Example
55    ///
56    /// ```rust
57    /// use dodecet_encoder::Dodecet;
58    ///
59    /// let d = unsafe { Dodecet::from_hex_unchecked(0xABC) };
60    /// assert_eq!(d.value(), 0xABC);
61    /// ```
62    #[inline]
63    pub unsafe fn from_hex_unchecked(value: u16) -> Self {
64        Dodecet { value }
65    }
66
67    /// Create a dodecet from a hex value
68    ///
69    /// # Example
70    ///
71    /// ```rust
72    /// use dodecet_encoder::Dodecet;
73    ///
74    /// let d = Dodecet::from_hex(0xABC);
75    /// assert_eq!(d.value(), 0xABC);
76    /// ```
77    #[inline]
78    pub const fn from_hex(value: u16) -> Self {
79        Dodecet {
80            value: value & MAX_DODECET,
81        }
82    }
83
84    /// Create a dodecet from a signed i16 value (-2048 to 2047)
85    ///
86    /// # Example
87    ///
88    /// ```rust
89    /// use dodecet_encoder::Dodecet;
90    ///
91    /// let d = Dodecet::from_signed(-100);
92    /// assert_eq!(d.as_signed(), -100);
93    /// ```
94    #[inline]
95    pub fn from_signed(value: i16) -> Self {
96        let unsigned = if value < 0 {
97            (value + 4096) as u16
98        } else {
99            value as u16
100        };
101        Dodecet {
102            value: unsigned & MAX_DODECET,
103        }
104    }
105
106    /// Get the raw value
107    ///
108    /// # Example
109    ///
110    /// ```rust
111    /// use dodecet_encoder::Dodecet;
112    ///
113    /// let d = Dodecet::from_hex(0xABC);
114    /// assert_eq!(d.value(), 0xABC);
115    /// ```
116    #[inline]
117    pub fn value(self) -> u16 {
118        self.value
119    }
120
121    /// Get a specific nibble (0, 1, or 2)
122    ///
123    /// # Arguments
124    /// * `index` - Nibble index (0 = LSB, 2 = MSB)
125    ///
126    /// # Example
127    ///
128    /// ```rust
129    /// use dodecet_encoder::Dodecet;
130    ///
131    /// let d = Dodecet::from_hex(0xABC);
132    /// assert_eq!(d.nibble(0).unwrap(), 0xC);
133    /// assert_eq!(d.nibble(1).unwrap(), 0xB);
134    /// assert_eq!(d.nibble(2).unwrap(), 0xA);
135    /// ```
136    #[inline]
137    pub fn nibble(self, index: u8) -> Result<u8> {
138        if index >= NIBBLES {
139            return Err(DodecetError::InvalidNibble);
140        }
141        Ok(((self.value >> (index * 4)) & 0xF) as u8)
142    }
143
144    /// Set a specific nibble
145    ///
146    /// # Arguments
147    /// * `index` - Nibble index (0 = LSB, 2 = MSB)
148    /// * `nibble` - New nibble value (0-15)
149    ///
150    /// # Example
151    ///
152    /// ```rust
153    /// use dodecet_encoder::Dodecet;
154    ///
155    /// let mut d = Dodecet::from_hex(0xABC);
156    /// d.set_nibble(0, 0xD).unwrap();
157    /// assert_eq!(d.value(), 0xABD);
158    /// ```
159    #[inline]
160    pub fn set_nibble(&mut self, index: u8, nibble: u8) -> Result<()> {
161        if index >= NIBBLES {
162            return Err(DodecetError::InvalidNibble);
163        }
164        if nibble > 0xF {
165            return Err(DodecetError::Overflow);
166        }
167
168        let mask = !(0xFu16 << (index * 4));
169        self.value = (self.value & mask) | ((nibble as u16) << (index * 4));
170        Ok(())
171    }
172
173    /// Check if dodecet is zero
174    ///
175    /// # Example
176    ///
177    /// ```rust
178    /// use dodecet_encoder::Dodecet;
179    ///
180    /// assert!(Dodecet::from_hex(0).is_zero());
181    /// assert!(!Dodecet::from_hex(0xABC).is_zero());
182    /// ```
183    #[inline]
184    pub fn is_zero(self) -> bool {
185        self.value == 0
186    }
187
188    /// Check if dodecet is at maximum value
189    ///
190    /// # Example
191    ///
192    /// ```rust
193    /// use dodecet_encoder::Dodecet;
194    ///
195    /// assert!(Dodecet::from_hex(0xFFF).is_max());
196    /// assert!(!Dodecet::from_hex(0xABC).is_max());
197    /// ```
198    #[inline]
199    pub fn is_max(self) -> bool {
200        self.value == MAX_DODECET
201    }
202
203    /// Count set bits (population count)
204    ///
205    /// # Example
206    ///
207    /// ```rust
208    /// use dodecet_encoder::Dodecet;
209    ///
210    /// let d = Dodecet::from_hex(0xFFF); // All bits set
211    /// assert_eq!(d.count_ones(), 12);
212    /// ```
213    #[inline]
214    pub fn count_ones(self) -> u32 {
215        self.value.count_ones()
216    }
217
218    /// Count unset bits
219    ///
220    /// # Example
221    ///
222    /// ```rust
223    /// use dodecet_encoder::Dodecet;
224    ///
225    /// let d = Dodecet::from_hex(0x000); // No bits set
226    /// // Note: count_zeros() operates on underlying u16, returns 16 for 0x000
227    // assert_eq!(d.count_zeros(), 16);
228    /// ```
229    #[inline]
230    pub fn count_zeros(self) -> u32 {
231        self.value.count_zeros()
232    }
233
234    /// Bitwise AND
235    #[inline]
236    pub fn and(self, other: Dodecet) -> Dodecet {
237        Dodecet {
238            value: self.value & other.value,
239        }
240    }
241
242    /// Bitwise OR
243    #[inline]
244    pub fn or(self, other: Dodecet) -> Dodecet {
245        Dodecet {
246            value: self.value | other.value,
247        }
248    }
249
250    /// Bitwise XOR
251    #[inline]
252    pub fn xor(self, other: Dodecet) -> Dodecet {
253        Dodecet {
254            value: self.value ^ other.value,
255        }
256    }
257
258    /// Bitwise NOT
259    #[inline]
260    pub fn not(self) -> Dodecet {
261        Dodecet {
262            value: (!self.value) & MAX_DODECET,
263        }
264    }
265
266    /// Arithmetic addition with wrapping
267    #[inline]
268    pub fn wrapping_add(self, other: Dodecet) -> Dodecet {
269        Dodecet {
270            value: self.value.wrapping_add(other.value) & MAX_DODECET,
271        }
272    }
273
274    /// Arithmetic subtraction with wrapping
275    #[inline]
276    pub fn wrapping_sub(self, other: Dodecet) -> Dodecet {
277        Dodecet {
278            value: self.value.wrapping_sub(other.value) & MAX_DODECET,
279        }
280    }
281
282    /// Arithmetic multiplication with wrapping
283    #[inline]
284    pub fn wrapping_mul(self, other: Dodecet) -> Dodecet {
285        Dodecet {
286            value: self.value.wrapping_mul(other.value) & MAX_DODECET,
287        }
288    }
289
290    /// Convert to hex string (3 characters)
291    ///
292    /// # Example
293    ///
294    /// ```rust
295    /// use dodecet_encoder::Dodecet;
296    ///
297    /// let d = Dodecet::from_hex(0xABC);
298    /// assert_eq!(d.to_hex_string(), "ABC");
299    /// ```
300    pub fn to_hex_string(self) -> String {
301        format!("{:03X}", self.value)
302    }
303
304    /// Parse from hex string
305    ///
306    /// # Example
307    ///
308    /// ```rust
309    /// use dodecet_encoder::Dodecet;
310    ///
311    /// let d = Dodecet::from_hex_str("ABC").unwrap();
312    /// assert_eq!(d.value(), 0xABC);
313    /// ```
314    pub fn from_hex_str(s: &str) -> Result<Self> {
315        let value = u16::from_str_radix(s.trim(), 16)
316            .map_err(|_| DodecetError::InvalidHex)?;
317
318        if value > MAX_DODECET {
319            return Err(DodecetError::Overflow);
320        }
321
322        Ok(Dodecet { value })
323    }
324
325    /// Convert to binary string (12 characters)
326    ///
327    /// # Example
328    ///
329    /// ```rust
330    /// use dodecet_encoder::Dodecet;
331    ///
332    /// let d = Dodecet::from_hex(0xABC);
333    /// assert_eq!(d.to_binary_string(), "101010111100");
334    /// ```
335    pub fn to_binary_string(self) -> String {
336        format!("{:012b}", self.value)
337    }
338
339    /// Geometric interpretation: Treat as signed value (-2048 to 2047)
340    ///
341    /// # Example
342    ///
343    /// ```rust
344    /// use dodecet_encoder::Dodecet;
345    ///
346    /// let d = Dodecet::from_hex(0x800);
347    /// assert_eq!(d.as_signed(), -2048);
348    /// ```
349    #[inline]
350    pub fn as_signed(self) -> i16 {
351        if self.value & 0x800 != 0 {
352            (self.value as i16) - 4096
353        } else {
354            self.value as i16
355        }
356    }
357
358    /// Normalize to floating point [0.0, 1.0]
359    ///
360    /// # Example
361    ///
362    /// ```rust
363    /// use dodecet_encoder::Dodecet;
364    ///
365    /// let d = Dodecet::from_hex(0x800); // Midpoint
366    /// assert!((d.normalize() - 0.5).abs() < 0.001);
367    /// ```
368    #[inline]
369    pub fn normalize(self) -> f64 {
370        self.value as f64 / MAX_DODECET as f64
371    }
372}
373
374
375impl From<u8> for Dodecet {
376    fn from(value: u8) -> Self {
377        Dodecet { value: value as u16 }
378    }
379}
380
381impl TryFrom<u16> for Dodecet {
382    type Error = DodecetError;
383
384    fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
385        Dodecet::new(value)
386    }
387}
388
389impl From<Dodecet> for u16 {
390    fn from(d: Dodecet) -> Self {
391        d.value
392    }
393}
394
395impl std::fmt::Display for Dodecet {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        write!(f, "0x{:03X}", self.value)
398    }
399}
400
401impl std::fmt::Binary for Dodecet {
402    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        write!(f, "{:012b}", self.value)
404    }
405}
406
407impl std::fmt::Octal for Dodecet {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        write!(f, "{:04o}", self.value)
410    }
411}
412
413impl std::ops::Add for Dodecet {
414    type Output = Self;
415
416    fn add(self, other: Self) -> Self::Output {
417        self.wrapping_add(other)
418    }
419}
420
421impl std::ops::Sub for Dodecet {
422    type Output = Self;
423
424    fn sub(self, other: Self) -> Self::Output {
425        self.wrapping_sub(other)
426    }
427}
428
429impl std::ops::Mul for Dodecet {
430    type Output = Self;
431
432    fn mul(self, other: Self) -> Self::Output {
433        self.wrapping_mul(other)
434    }
435}
436
437impl std::ops::BitAnd for Dodecet {
438    type Output = Self;
439
440    fn bitand(self, other: Self) -> Self::Output {
441        self.and(other)
442    }
443}
444
445impl std::ops::BitOr for Dodecet {
446    type Output = Self;
447
448    fn bitor(self, other: Self) -> Self::Output {
449        self.or(other)
450    }
451}
452
453impl std::ops::BitXor for Dodecet {
454    type Output = Self;
455
456    fn bitxor(self, other: Self) -> Self::Output {
457        self.xor(other)
458    }
459}
460
461impl std::ops::Not for Dodecet {
462    type Output = Self;
463
464    fn not(self) -> Self::Output {
465        self.not()
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[test]
474    fn test_creation() {
475        let d = Dodecet::new(0xABC).unwrap();
476        assert_eq!(d.value(), 0xABC);
477
478        let d2 = Dodecet::from_hex(0xDEF);
479        assert_eq!(d2.value(), 0xDEF);
480    }
481
482    #[test]
483    fn test_nibbles() {
484        let d = Dodecet::from_hex(0xABC);
485        assert_eq!(d.nibble(0).unwrap(), 0xC);
486        assert_eq!(d.nibble(1).unwrap(), 0xB);
487        assert_eq!(d.nibble(2).unwrap(), 0xA);
488    }
489
490    #[test]
491    fn test_set_nibble() {
492        let mut d = Dodecet::from_hex(0xABC);
493        d.set_nibble(0, 0xD).unwrap();
494        assert_eq!(d.value(), 0xABD);
495
496        d.set_nibble(1, 0xE).unwrap();
497        assert_eq!(d.value(), 0xAED);
498
499        d.set_nibble(2, 0x1).unwrap();
500        assert_eq!(d.value(), 0x1ED);
501    }
502
503    #[test]
504    fn test_overflow() {
505        assert!(Dodecet::new(0x1000).is_err());
506        assert!(Dodecet::new(0xFFF).is_ok());
507    }
508
509    #[test]
510    fn test_bitwise_ops() {
511        let a = Dodecet::from_hex(0xF0F);
512        let b = Dodecet::from_hex(0x0F0);
513
514        assert_eq!((a & b).value(), 0x000);
515        assert_eq!((a | b).value(), 0xFFF);
516        assert_eq!((a ^ b).value(), 0xFFF);
517        assert_eq!((!a).value(), 0x0F0);
518    }
519
520    #[test]
521    fn test_arithmetic() {
522        let a = Dodecet::from_hex(0x800);
523        let b = Dodecet::from_hex(0x800);
524
525        let c = a + b;
526        assert_eq!(c.value(), 0x000); // Wraps around
527
528        let d = Dodecet::from_hex(0x100) - Dodecet::from_hex(0x001);
529        assert_eq!(d.value(), 0x0FF);
530    }
531
532    #[test]
533    fn test_conversions() {
534        let d = Dodecet::from_hex(0xABC);
535
536        assert_eq!(d.to_hex_string(), "ABC");
537        assert_eq!(d.to_binary_string(), "101010111100");
538
539        let d2 = Dodecet::from_hex_str("ABC").unwrap();
540        assert_eq!(d2.value(), 0xABC);
541    }
542
543    #[test]
544    fn test_signed() {
545        let d = Dodecet::from_hex(0x800);
546        assert_eq!(d.as_signed(), -2048);
547
548        let d = Dodecet::from_hex(0x7FF);
549        assert_eq!(d.as_signed(), 2047);
550
551        let d = Dodecet::from_hex(0x000);
552        assert_eq!(d.as_signed(), 0);
553    }
554
555    #[test]
556    fn test_normalize() {
557        let d = Dodecet::from_hex(0x000);
558        assert_eq!(d.normalize(), 0.0);
559
560        let d = Dodecet::from_hex(0xFFF);
561        assert!((d.normalize() - 1.0).abs() < f64::EPSILON);
562
563        let d = Dodecet::from_hex(0x800);
564        assert!((d.normalize() - 0.5).abs() < 0.001);
565    }
566
567    #[test]
568    fn test_count_bits() {
569        let d = Dodecet::from_hex(0xFFF);
570        assert_eq!(d.count_ones(), 12);
571        // count_zeros() on u16 returns 16 - count_ones(), not 12 - count_ones()
572        assert_eq!(d.count_zeros(), 4);
573
574        let d = Dodecet::from_hex(0x000);
575        assert_eq!(d.count_ones(), 0);
576        assert_eq!(d.count_zeros(), 16);
577    }
578
579    #[test]
580    fn test_display() {
581        let d = Dodecet::from_hex(0xABC);
582        assert_eq!(format!("{}", d), "0xABC");
583        assert_eq!(format!("{:b}", d), "101010111100");
584        assert_eq!(format!("{:o}", d), "5274");
585    }
586}