1use num_traits::AsPrimitive;
4use serde::{Deserialize, Serialize};
5
6use crate::CompactStorage;
7
8#[derive(
25 Default, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Copy, Clone, Serialize, Deserialize,
26)]
27#[repr(transparent)]
28pub struct Radix(u8);
29
30impl<T: AsPrimitive<u8> + From<u8> + PartialOrd> From<T> for Radix {
31 #[track_caller]
32 fn from(value: T) -> Self {
33 assert!(value >= 2u8.into(), "Radices cannot be less than 2.");
34 assert!(value <= 255u8.into(), "Radix overflow.");
35 Radix(value.as_())
36 }
37}
38
39impl CompactStorage for Radix {
40 type InlineType = Radix;
41 const CONVERSION_INFALLIBLE: bool = true;
42
43 #[inline(always)]
44 fn to_inline(value: Self) -> Result<Self::InlineType, Self> {
45 Ok(value)
46 }
47
48 #[inline(always)]
49 fn from_inline(value: Self::InlineType) -> Self {
50 value
51 }
52
53 #[inline(always)]
54 fn to_inline_unchecked(value: Self) -> Self::InlineType {
55 value
56 }
57}
58
59impl From<Radix> for usize {
60 #[inline(always)]
61 fn from(value: Radix) -> Self {
62 value.0 as usize
63 }
64}
65
66impl From<Radix> for u64 {
67 #[inline(always)]
68 fn from(value: Radix) -> Self {
69 value.0 as u64
70 }
71}
72
73impl From<Radix> for u32 {
74 #[inline(always)]
75 fn from(value: Radix) -> Self {
76 value.0 as u32
77 }
78}
79
80impl From<Radix> for u16 {
81 #[inline(always)]
82 fn from(value: Radix) -> Self {
83 value.0 as u16
84 }
85}
86
87impl From<Radix> for u8 {
88 #[inline(always)]
89 fn from(value: Radix) -> Self {
90 value.0
91 }
92}
93
94impl From<Radix> for u128 {
95 #[inline(always)]
96 fn from(value: Radix) -> Self {
97 value.0 as u128
98 }
99}
100
101impl From<Radix> for i16 {
102 #[inline(always)]
103 fn from(value: Radix) -> Self {
104 value.0 as i16
105 }
106}
107
108impl From<Radix> for i32 {
109 #[inline(always)]
110 fn from(value: Radix) -> Self {
111 value.0 as i32
112 }
113}
114
115impl From<Radix> for i64 {
116 #[inline(always)]
117 fn from(value: Radix) -> Self {
118 value.0 as i64
119 }
120}
121
122impl From<Radix> for i128 {
123 #[inline(always)]
124 fn from(value: Radix) -> Self {
125 value.0 as i128
126 }
127}
128
129impl From<Radix> for f32 {
130 #[inline(always)]
131 fn from(value: Radix) -> Self {
132 value.0 as f32
133 }
134}
135
136impl From<Radix> for f64 {
137 #[inline(always)]
138 fn from(value: Radix) -> Self {
139 value.0 as f64
140 }
141}
142
143impl std::iter::Product<Radix> for usize {
144 fn product<I: Iterator<Item = Radix>>(iter: I) -> usize {
145 iter.into_iter().map(|r| r.0 as usize).product()
146 }
147}
148
149impl<'a> std::iter::Product<&'a Radix> for usize {
150 fn product<I: Iterator<Item = &'a Radix>>(iter: I) -> usize {
151 iter.into_iter().map(|r| r.0 as usize).product()
152 }
153}
154
155impl PartialEq<usize> for Radix {
156 fn eq(&self, other: &usize) -> bool {
157 (self.0 as usize) == *other
158 }
159}
160
161impl std::fmt::Display for Radix {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 write!(f, "{}", self.0)
164 }
165}
166
167impl<T: Into<Radix>> std::ops::Add<T> for Radix {
168 type Output = Self;
169
170 fn add(self, rhs: T) -> Self::Output {
171 Radix(self.0 + rhs.into().0)
172 }
173}
174
175impl<T: Into<Radix>> std::ops::Sub<T> for Radix {
176 type Output = Self;
177
178 fn sub(self, rhs: T) -> Self::Output {
179 Radix(self.0 - rhs.into().0)
180 }
181}
182
183#[cfg(feature = "python")]
184mod python {
185 use super::*;
186 use pyo3::prelude::*;
187 use pyo3_stub_gen::impl_stub_type;
188
189 impl_stub_type!(Radix = u8);
190
191 impl<'py> IntoPyObject<'py> for Radix {
192 type Target = <u8 as IntoPyObject<'py>>::Target;
193 type Output = <u8 as IntoPyObject<'py>>::Output;
194 type Error = <u8 as IntoPyObject<'py>>::Error;
195
196 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
197 self.0.into_pyobject(py)
198 }
199 }
200
201 impl<'a, 'py> FromPyObject<'a, 'py> for Radix {
202 type Error = PyErr;
203
204 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
205 let value: u8 = obj.extract()?;
206 if value < 2 {
207 Err(pyo3::exceptions::PyValueError::new_err(format!(
208 "Radix value {} is invalid. Radices must be >= 2.",
209 value
210 )))
211 } else {
212 Ok(Radix(value))
213 }
214 }
215 }
216
217 #[cfg(test)]
218 mod tests {
219 use super::*;
220 use pyo3::Python;
221
222 #[test]
223 fn test_into_pyobject() {
224 Python::initialize();
225 Python::attach(|py| {
226 let radix = Radix::from(10u8);
227 let py_obj = radix.into_pyobject(py).unwrap();
228 let value: u8 = py_obj.extract().unwrap();
229 assert_eq!(value, 10);
230 });
231 }
232
233 #[test]
234 fn test_from_pyobject() {
235 Python::initialize();
236 Python::attach(|py| {
237 let py_int = 16u8.into_pyobject(py).unwrap();
238 let radix: Radix = py_int.extract().unwrap();
239 assert_eq!(usize::from(radix), 16);
240 });
241 }
242
243 #[test]
244 fn test_from_pyobject_invalid() {
245 Python::initialize();
246 Python::attach(|py| {
247 let py_int = 1u8.into_pyobject(py).unwrap();
248 let result: PyResult<Radix> = py_int.extract();
249 assert!(result.is_err());
250 let err = result.unwrap_err();
251 assert!(err.to_string().contains("Radix value 1 is invalid"));
252 });
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn valid_radix_creation() {
263 let r2 = Radix::from(2u8);
264 let r10 = Radix::from(10u8);
265 let r255 = Radix::from(255u8);
266
267 assert_eq!(usize::from(r2), 2);
268 assert_eq!(usize::from(r10), 10);
269 assert_eq!(usize::from(r255), 255);
270 }
271
272 #[test]
273 #[should_panic(expected = "Radices cannot be less than 2")]
274 fn invalid_radix_too_small() {
275 let _r = Radix::from(1u8);
276 }
277
278 #[test]
279 fn conversions() {
280 let r16 = Radix::from(16u8);
281 assert_eq!(usize::from(r16), 16);
282 assert_eq!(u64::from(r16), 16u64);
283 }
284
285 #[test]
286 fn arithmetic() {
287 let r10 = Radix::from(10u8);
288 let r5 = Radix::from(5u8);
289
290 assert_eq!(r10 + r5, Radix::from(15u8));
291 assert_eq!(r10 - r5, Radix::from(5u8));
292 }
293
294 #[test]
295 fn display() {
296 let r16 = Radix::from(16u8);
297 assert_eq!(format!("{}", r16), "16");
298 }
299
300 #[test]
301 fn equality() {
302 let r10 = Radix::from(10u8);
303 assert_eq!(r10, 10usize);
304 }
305
306 #[test]
307 fn product() {
308 let radices = [Radix::from(2u8), Radix::from(3u8), Radix::from(4u8)];
309 let product: usize = radices.iter().product();
310 assert_eq!(product, 24);
311 }
312}