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