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}