1pub const MAX_NIBBLES: u8 = 16;
15
16#[must_use]
20pub fn from_bcd_byte(byte: u8) -> Option<u8> {
21 bcd_to_decimal(u64::from(byte), 2).map(|v| v as u8)
22}
23
24#[must_use]
28pub fn to_bcd_byte(value: u8) -> Option<u8> {
29 decimal_to_bcd(u64::from(value), 2).map(|v| v as u8)
30}
31
32#[must_use]
38pub fn bcd_to_decimal(raw: u64, nibbles: u8) -> Option<u64> {
39 if nibbles > MAX_NIBBLES {
40 return None;
41 }
42 let mut acc = 0u64;
43 for i in (0..nibbles).rev() {
44 let digit = (raw >> (i * 4)) & 0x0F;
45 if digit > 9 {
46 return None;
47 }
48 acc = acc * 10 + digit;
49 }
50 Some(acc)
51}
52
53#[must_use]
58pub fn decimal_to_bcd(value: u64, nibbles: u8) -> Option<u64> {
59 if nibbles > MAX_NIBBLES {
60 return None;
61 }
62 let mut packed = 0u64;
63 let mut remaining = value;
64 for i in 0..nibbles {
65 let digit = remaining % 10;
66 packed |= digit << (i * 4);
67 remaining /= 10;
68 }
69 if remaining != 0 {
70 return None;
72 }
73 Some(packed)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn byte_round_trips_across_full_range() {
82 for v in 0..=99u8 {
83 let bcd = to_bcd_byte(v).expect("0..=99 encodes");
84 assert_eq!(from_bcd_byte(bcd), Some(v), "round-trip {v}");
85 }
86 }
87
88 #[test]
89 fn from_bcd_byte_rejects_non_decimal_nibbles() {
90 assert_eq!(from_bcd_byte(0x1A), None);
91 assert_eq!(from_bcd_byte(0xA1), None);
92 assert_eq!(from_bcd_byte(0x99), Some(99));
93 }
94
95 #[test]
96 fn to_bcd_byte_rejects_over_99() {
97 assert_eq!(to_bcd_byte(100), None);
98 assert_eq!(to_bcd_byte(99), Some(0x99));
99 }
100
101 #[test]
102 fn multi_nibble_round_trips() {
103 let raw = 0x1172_5000u64;
105 assert_eq!(bcd_to_decimal(raw, 8), Some(11_725_000));
106 assert_eq!(decimal_to_bcd(11_725_000, 8), Some(raw));
107 }
108
109 #[test]
110 fn bcd_to_decimal_rejects_bad_nibble() {
111 assert_eq!(bcd_to_decimal(0x000A_0000, 8), None);
112 }
113
114 #[test]
115 fn decimal_to_bcd_rejects_overflow() {
116 assert_eq!(decimal_to_bcd(100_000_000, 8), None);
118 assert_eq!(decimal_to_bcd(99_999_999, 8), Some(0x9999_9999));
119 }
120}