padded_number/
section.rs

1use crate::*;
2
3impl<const MIN: u8, const MAX: u8> PaddedNumber<MIN, MAX> {
4    /// Get a section of a padded number, missing digits not allowed.
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 number whose
11    /// length is less than `END_INDEX`.
12    ///
13    /// Returned max and min sizes always be further relaxed with
14    /// [`PaddedNumber::resize`].
15    ///
16    /// # Examples
17    ///
18    /// ```rust
19    /// #![feature(generic_const_exprs)]
20    ///
21    /// # use padded_number_macros::*;
22    /// let section = padded_number!("00123")
23    ///     .checked_section::<2, 5>()
24    ///     .expect("section should not have overflowed");
25    ///
26    /// assert_eq!(section, bound_padded_number!(3, 3, "123"));
27    ///
28    /// let section = bound_padded_number!(1, 3, "0").checked_section::<1, 3>();
29    /// // overflowed, missing two digits after "0"
30    /// assert!(section.is_none());
31    /// ```
32    ///
33    /// ```compile_fail
34    /// #![feature(generic_const_exprs)]
35    ///
36    /// # use padded_number_macros::*;
37    /// let section = bound_padded_number!(3, 3, "123");
38    /// section.checked_section::<0, 4>(); // <-- END_INDEX '4' > MAX_LENGTH '3'
39    /// ```
40    ///
41    /// ```compile_fail
42    /// #![feature(generic_const_exprs)]
43    ///
44    /// # use padded_number_macros::*;
45    /// let padded_number = bound_padded_number!(3, 3, "123");
46    /// section.checked_section::<2, 1>(); // <-- END_INDEX '1' < START_INDEX '2'
47    /// ```
48    pub fn checked_section<const START_INDEX: u8, const END_INDEX: u8>(
49        &self,
50    ) -> Option<PaddedNumber<{ END_INDEX - START_INDEX }, { END_INDEX - START_INDEX }>>
51    where
52        // Expresses "END_INDEX <= MAX_LENGTH" with current
53        // `generic_const_exprs` unstable feature capabilities
54        [(); { MAX - END_INDEX } as usize]:,
55    {
56        if END_INDEX > self.len() {
57            return None;
58        }
59
60        let (leading_zeros, number) = section_impl(self.leading_zeros, self.number, START_INDEX, END_INDEX);
61        Some(PaddedNumber { leading_zeros, number })
62    }
63
64    /// Get a section of a padded number, missing digits allowed.
65    ///
66    /// `START`, as the names suggests, denotes the start index on the padded
67    /// number to select from, inclusive. `END` index is on the other hand
68    /// exclusive.
69    ///
70    /// Remaining bounds are statically verified. E.g.:
71    /// - `START` < `END`,
72    /// - `NEW_MIN` <= `END` - `START`
73    ///
74    /// Returns `None` if the returned section length would be less than the
75    /// specified `NEW_MIN` parameter. As for the maximum length, `END - START`
76    /// is provided from the method. [`PaddedNumber::resize`] can then be used
77    /// to relax these bounds even further.
78    ///
79    /// # Examples
80    ///
81    /// ```rust
82    /// #![feature(generic_const_exprs)]
83    ///
84    /// # use padded_number_macros::*;
85    /// let section = padded_number!("00123")
86    ///     .relaxed_section::<3, 10, 1>()
87    ///     .expect("invalid min length");
88    ///
89    /// assert_eq!(section, bound_padded_number!(1, 7, "23"));
90    ///
91    /// let section = bound_padded_number!(1, 10, "00").relaxed_section::<5, 7, 1>();
92    /// // no digits between index 5 and 7, and at least 1 was required
93    /// assert!(section.is_none());
94    /// ```
95    ///
96    /// ```compile_fail
97    /// #![feature(generic_const_exprs)]
98    ///
99    /// # use padded_number_macros::*;
100    /// let section = bound_padded_number!(1, 5, "123");
101    /// section.checked_section::<2, 3, 10>(); // <-- NEW_MIN '10' > END '2' - START '1'
102    /// ```
103    pub fn relaxed_section<const START: u8, const END: u8, const NEW_MIN: u8>(
104        &self,
105    ) -> Option<PaddedNumber<NEW_MIN, { END - START }>>
106    where
107        [(); { MAX - END } as usize]:,
108        [(); { (END - START) - NEW_MIN } as usize]:,
109    {
110        let remaining_length = self.len().saturating_sub(START);
111
112        if remaining_length < NEW_MIN {
113            return None;
114        }
115
116        if remaining_length == 0 {
117            return Some(PaddedNumber { leading_zeros: 0, number: 0 });
118        }
119
120        let (leading_zeros, number) = section_impl(self.leading_zeros, self.number, START, START + remaining_length);
121
122        Some(PaddedNumber { leading_zeros, number })
123    }
124
125    /// Get a section from the minimum length of a padded number
126    ///
127    /// Unlike [`PaddedNumber::checked_section`], this does not need to return
128    /// an option. Type system ensures that END_INDEX <= MIN_LENGTH.
129    ///
130    /// # Examples
131    ///
132    /// ```rust
133    /// #![feature(generic_const_exprs)]
134    ///
135    /// # use padded_number_macros::*;
136    /// let section = bound_padded_number!(3, 5, "00123").expected_section::<0, 3>();
137    /// assert_eq!(section, bound_padded_number!(3, 3, "001"));
138    /// ```
139    ///
140    /// ```compile_fail
141    /// #![feature(generic_const_exprs)]
142    ///
143    /// # use padded_number_macros::*;
144    /// let section = bound_padded_number!(3, 5, "00123");
145    /// section.expected_section::<0, 4>(); // <-- END_INDEX '4' > MIN_LENGTH '3'
146    /// ```
147    pub fn expected_section<const START_INDEX: u8, const END_INDEX: u8>(
148        &self,
149    ) -> PaddedNumber<{ END_INDEX - START_INDEX }, { END_INDEX - START_INDEX }>
150    where
151        // Expresses "END_INDEX <= MIN_LENGTH" with current
152        // `generic_const_exprs` unstable feature capabilities
153        [(); { MIN - END_INDEX } as usize]:,
154        // Bound required to call `Self::section_impl`
155        [(); { MAX - END_INDEX } as usize]:,
156    {
157        // safe to call since end <= min_length
158        let (leading_zeros, number) = section_impl(self.leading_zeros, self.number, START_INDEX, END_INDEX);
159        PaddedNumber { leading_zeros, number }
160    }
161}
162
163/// # Panics
164/// - If !(start <= end <= remaining_number_length)
165fn section_impl(leading_zeros: u8, remaining_number: u64, start: u8, end: u8) -> (u8, u64) {
166    match (start.checked_sub(leading_zeros), end.checked_sub(leading_zeros)) {
167        (Some(translated_start), Some(translated_end)) => {
168            (0, number_subsection(remaining_number, translated_start, translated_end))
169        }
170        (None, Some(translated_end)) => (
171            leading_zeros - start,
172            number_subsection(remaining_number, 0, translated_end),
173        ),
174        (None, None) => {
175            let leading_zero_start = leading_zeros - start;
176            let leading_zero_end = leading_zeros - end;
177            (leading_zero_start - leading_zero_end, 0)
178        }
179        (Some(_), None) => {
180            panic!("encountered start > end")
181        }
182    }
183}
184
185/// # Panics
186/// - If start > number_length
187/// - If end > number_length
188pub(crate) const fn number_subsection(number: u64, start: u8, end: u8) -> u64 {
189    let number_length = utils::number_len(number);
190
191    if number_length == 0 {
192        return 0;
193    }
194
195    let left_shifts = start;
196    let right_shifts = number_length - end;
197
198    let mut number = number;
199    number = left_shift_repeated(number, number_length, left_shifts);
200    number = right_shift_repeated(number, right_shifts);
201
202    return number;
203
204    /// # Panics
205    /// - If repetitions > number_length
206    const fn left_shift_repeated(number: u64, number_length: u8, repetitions: u8) -> u64 {
207        let mut current = number;
208        let mut current_length = number_length;
209        let mut repetitions_left = repetitions;
210
211        while repetitions_left > 0 {
212            current = left_shift(current, current_length);
213            current_length -= 1;
214            repetitions_left -= 1;
215        }
216
217        current
218    }
219
220    /// # Panics
221    /// - If number_length == 0
222    const fn left_shift(number: u64, number_length: u8) -> u64 {
223        let decimal = 10u64.pow((number_length - 1) as u32);
224        number - (number / decimal) * decimal
225    }
226
227    const fn right_shift_repeated(number: u64, repetitions: u8) -> u64 {
228        number / 10u64.pow(repetitions as u32)
229    }
230}