qudit_core/
radix.rs

1//! Radix (number base) representation with compile-time validation.
2
3use num_traits::AsPrimitive;
4
5use crate::CompactStorage;
6
7/// A validated radix (number base) value between 2 and 255 inclusive.
8///
9/// This type ensures that radix values are always valid for numeric base conversions.
10///
11/// # Examples
12///
13/// ```
14/// # use qudit_core::Radix;
15/// // Create from u8 or usize
16/// let radix = Radix::from(10u8);
17/// let radix = Radix::from(16usize);
18///
19/// // Convert to other integer types
20/// let base = usize::from(radix);
21/// let base: u64 = radix.into();
22/// ```
23#[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}