Skip to main content

qudit_core/
radix.rs

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