padded_number/
section.rs

1use crate::*;
2
3impl<const MIN_LENGTH: u8, const MAX_LENGTH: u8> PaddedNumber<MIN_LENGTH, MAX_LENGTH> {
4    /// Get a section of a padded number
5    ///
6    /// First generic parameter is the start index, inclusive. Second parameter
7    /// denotes the end index, exclusive. Remaining bound checks are enforced by
8    /// the type system. E.g. end >= start and end <= max length.
9    ///
10    /// Returns `None` if the end index overflowed for a padded whose length is
11    /// is not equal to it's max length.
12    ///
13    /// # Examples
14    ///
15    /// ```rust
16    /// #![feature(generic_const_exprs)]
17    ///
18    /// # use padded_number_macros::*;
19    /// let section = padded_number!("00123")
20    ///     .section::<2, 5>()
21    ///     .expect("section should not have overflowed");
22    ///
23    /// assert_eq!(section, bound_padded_number!(3, 3, "123"));
24    ///
25    /// let section = bound_padded_number!(1, 3, "0").section::<1, 3>();
26    /// // overflowed, missing two digits after "0"
27    /// assert!(section.is_none());
28    /// ```
29    ///
30    /// ```compile_fail
31    /// #![feature(generic_const_exprs)]
32    ///
33    /// # use padded_number_macros::*;
34    /// let section = bound_padded_number!(3, 3, "123");
35    /// section.section::<0, 4>(); // <-- END_INDEX '4' > MAX_LENGTH '3'
36    /// ```
37    ///
38    /// ```compile_fail
39    /// #![feature(generic_const_exprs)]
40    ///
41    /// # use padded_number_macros::*;
42    /// let section = bound_padded_number!(3, 3, "123");
43    /// section.section::<2, 1>(); // <-- END_INDEX '1' < START_INDEX '2'
44    /// ```
45    pub fn section<const START_INDEX: u8, const END_INDEX: u8>(
46        &self,
47    ) -> Option<PaddedNumber<{ END_INDEX - START_INDEX }, { END_INDEX - START_INDEX }>>
48    where
49        // Expresses "END_INDEX <= MAX_LENGTH" with current
50        // `generic_const_exprs` unstable feature capabilities
51        [(); { MAX_LENGTH - END_INDEX } as usize]:,
52    {
53        if END_INDEX > self.len() {
54            return None;
55        }
56
57        Some(self.section_impl())
58    }
59
60    /// Get a section from the minimum length of a padded number
61    ///
62    /// Unlike [`PaddedNumber::section`], this does not need to return an
63    /// option. Type system ensures that END_INDEX <= MIN_LENGTH.
64    ///
65    /// # Examples
66    ///
67    /// ```rust
68    /// #![feature(generic_const_exprs)]
69    ///
70    /// # use padded_number_macros::*;
71    /// let section = bound_padded_number!(3, 5, "00123").expected_section::<0, 3>();
72    /// assert_eq!(section, bound_padded_number!(3, 3, "001"));
73    /// ```
74    ///
75    /// ```compile_fail
76    /// #![feature(generic_const_exprs)]
77    ///
78    /// # use padded_number_macros::*;
79    /// let section = bound_padded_number!(3, 5, "00123");
80    /// section.expected_section::<0, 4>(); // <-- END_INDEX '4' > MIN_LENGTH '3'
81    /// ```
82    pub fn expected_section<const START_INDEX: u8, const END_INDEX: u8>(
83        &self,
84    ) -> PaddedNumber<{ END_INDEX - START_INDEX }, { END_INDEX - START_INDEX }>
85    where
86        // Expresses "END_INDEX <= MIN_LENGTH" with current
87        // `generic_const_exprs` unstable feature capabilities
88        [(); { MIN_LENGTH - END_INDEX } as usize]:,
89        // Bound required to call `Self::section_impl`
90        [(); { MAX_LENGTH - END_INDEX } as usize]:,
91    {
92        // save to call since END_INDEX <= MIN_LENGTH
93        self.section_impl()
94    }
95
96    /// Assumes:
97    /// - END_INDEX <= self.len()
98    fn section_impl<const START_INDEX: u8, const END_INDEX: u8>(
99        &self,
100    ) -> PaddedNumber<{ END_INDEX - START_INDEX }, { END_INDEX - START_INDEX }>
101    where
102        // Expresses "END_INDEX <= MAX_LENGTH" with current
103        // `generic_const_exprs` unstable feature capabilities
104        [(); { MAX_LENGTH - END_INDEX } as usize]:,
105    {
106        let leading_zeros = self.leading_zeros;
107
108        let (new_leading_zeros, new_number) = match (
109            START_INDEX.checked_sub(leading_zeros),
110            END_INDEX.checked_sub(leading_zeros),
111        ) {
112            (Some(translated_start), Some(translated_end)) => {
113                (0, self.number_subsection(translated_start, translated_end))
114            }
115            (None, Some(translated_end)) => (leading_zeros - START_INDEX, self.number_subsection(0, translated_end)),
116            (None, None) => {
117                let leading_zero_start = leading_zeros - START_INDEX;
118                let leading_zero_end = leading_zeros - END_INDEX;
119                (leading_zero_start - leading_zero_end, 0)
120            }
121            (Some(_), None) => {
122                // should not be possible with const generic expression
123                // `{ END_INDEX - START_INDEX }` from method signature
124                unreachable!("encountered start > end")
125            }
126        };
127
128        PaddedNumber { leading_zeros: new_leading_zeros, number: new_number }
129    }
130
131    /// Assumes:
132    /// - start <= number_length
133    /// - end <= number_length
134    pub(crate) const fn number_subsection(&self, start: u8, end: u8) -> u64 {
135        let number_length = self.number_len();
136
137        if number_length == 0 {
138            return 0;
139        }
140
141        let left_shifts = start;
142        let right_shifts = number_length - end;
143
144        let mut number = self.number;
145        number = left_shift_repeated(number, number_length, left_shifts);
146        number = right_shift_repeated(number, right_shifts);
147
148        return number;
149
150        // assumes repetitions <= number_length, and number_length > 0
151        const fn left_shift_repeated(number: u64, number_length: u8, repetitions: u8) -> u64 {
152            let mut current = number;
153            let mut current_length = number_length;
154            let mut repetitions_left = repetitions;
155
156            while repetitions_left > 0 {
157                current = left_shift(current, current_length);
158                current_length -= 1;
159                repetitions_left -= 1;
160            }
161
162            current
163        }
164
165        // assumes number_length > 0
166        const fn left_shift(number: u64, number_length: u8) -> u64 {
167            let decimal = 10u64.pow((number_length - 1) as u32);
168            number - (number / decimal) * decimal
169        }
170
171        const fn right_shift_repeated(number: u64, repetitions: u8) -> u64 {
172            number / 10u64.pow(repetitions as u32)
173        }
174    }
175}