1use std::cmp::Ordering;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6
7#[derive(Clone, Copy, Debug)]
24pub enum Integer {
25 Signed(i64),
27 Unsigned(u64),
29}
30
31impl Integer {
32 #[must_use]
47 pub const fn as_signed(self) -> Option<i64> {
48 match self {
49 Self::Signed(value) => Some(value),
50 Self::Unsigned(value) => {
51 if value <= i64::MAX.cast_unsigned() {
52 Some(value.cast_signed())
53 } else {
54 None
55 }
56 }
57 }
58 }
59
60 #[must_use]
72 pub const fn as_unsigned(self) -> Option<u64> {
73 match self {
74 Self::Unsigned(value) => Some(value),
75 Self::Signed(value) => {
76 if value >= 0 {
77 Some(value.cast_unsigned())
78 } else {
79 None
80 }
81 }
82 }
83 }
84
85 #[cfg(any(test, feature = "binary", feature = "xml", feature = "openstep"))]
88 pub(crate) const fn to_raw_parts(self) -> (bool, u64) {
89 match self {
90 Self::Signed(value) => (true, value.cast_unsigned()),
91 Self::Unsigned(value) => (false, value),
92 }
93 }
94
95 fn widen(self) -> i128 {
96 match self {
97 Self::Signed(value) => i128::from(value),
98 Self::Unsigned(value) => i128::from(value),
99 }
100 }
101}
102
103impl PartialEq for Integer {
104 fn eq(&self, other: &Self) -> bool {
105 match (*self, *other) {
106 (Self::Signed(a), Self::Signed(b)) => a == b,
107 (Self::Unsigned(a), Self::Unsigned(b)) => a == b,
108 (Self::Signed(s), Self::Unsigned(u)) | (Self::Unsigned(u), Self::Signed(s)) => {
109 s >= 0 && s.cast_unsigned() == u
110 }
111 }
112 }
113}
114
115impl Eq for Integer {}
116
117impl Hash for Integer {
118 fn hash<H: Hasher>(&self, state: &mut H) {
119 match *self {
122 Self::Unsigned(value) => state.write_u64(value),
123 Self::Signed(value) if value >= 0 => state.write_u64(value.cast_unsigned()),
124 Self::Signed(value) => state.write_i64(value),
125 }
126 }
127}
128
129impl Ord for Integer {
130 fn cmp(&self, other: &Self) -> Ordering {
131 self.widen().cmp(&other.widen())
132 }
133}
134
135impl PartialOrd for Integer {
136 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
137 Some(self.cmp(other))
138 }
139}
140
141impl fmt::Display for Integer {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Self::Signed(value) => fmt::Display::fmt(value, f),
145 Self::Unsigned(value) => fmt::Display::fmt(value, f),
146 }
147 }
148}
149
150macro_rules! impl_from_signed {
151 ($($ty:ty),+) => {$(
152 impl From<$ty> for Integer {
153 fn from(value: $ty) -> Self {
154 Self::Signed(i64::from(value))
155 }
156 }
157 )+};
158}
159
160macro_rules! impl_from_unsigned {
161 ($($ty:ty),+) => {$(
162 impl From<$ty> for Integer {
163 fn from(value: $ty) -> Self {
164 Self::Unsigned(u64::from(value))
165 }
166 }
167 )+};
168}
169
170impl_from_signed!(i8, i16, i32, i64);
171impl_from_unsigned!(u8, u16, u32, u64);
172
173#[cfg(test)]
174mod tests {
175 use std::hash::{BuildHasher, RandomState};
176
177 use super::*;
178
179 fn hashes_equal(a: Integer, b: Integer) -> bool {
180 let state = RandomState::new();
181 state.hash_one(a) == state.hash_one(b)
182 }
183
184 #[test]
185 fn equality_is_numeric_across_variants() {
186 assert_eq!(Integer::Signed(5), Integer::Unsigned(5));
187 assert_eq!(Integer::Unsigned(5), Integer::Signed(5));
188 assert_eq!(Integer::Signed(0), Integer::Unsigned(0));
189 assert_ne!(Integer::Signed(-1), Integer::Unsigned(u64::MAX));
190 assert_ne!(
191 Integer::Signed(i64::MIN),
192 Integer::Unsigned(i64::MIN.unsigned_abs())
193 );
194 assert_eq!(
195 Integer::Signed(i64::MAX),
196 Integer::Unsigned(i64::MAX.cast_unsigned())
197 );
198 }
199
200 #[test]
201 fn equal_values_hash_equally() {
202 let pairs = [
203 (Integer::Signed(5), Integer::Unsigned(5)),
204 (Integer::Signed(0), Integer::Unsigned(0)),
205 (
206 Integer::Signed(i64::MAX),
207 Integer::Unsigned(i64::MAX.cast_unsigned()),
208 ),
209 ];
210 for (a, b) in pairs {
211 assert_eq!(a, b);
212 assert!(hashes_equal(a, b));
213 }
214 assert!(hashes_equal(
216 Integer::Signed(-1),
217 Integer::Unsigned(u64::MAX)
218 ));
219 assert_ne!(Integer::Signed(-1), Integer::Unsigned(u64::MAX));
220 }
221
222 #[test]
223 fn ordering_is_total_numeric_order() {
224 let mut values = [
225 Integer::Unsigned(u64::MAX),
226 Integer::Signed(-1),
227 Integer::Unsigned(0),
228 Integer::Signed(i64::MIN),
229 Integer::Unsigned(i64::MAX.cast_unsigned() + 1),
230 Integer::Signed(i64::MAX),
231 ];
232 values.sort_unstable();
233 assert_eq!(
234 values,
235 [
236 Integer::Signed(i64::MIN),
237 Integer::Signed(-1),
238 Integer::Unsigned(0),
239 Integer::Signed(i64::MAX),
240 Integer::Unsigned(i64::MAX.cast_unsigned() + 1),
241 Integer::Unsigned(u64::MAX),
242 ]
243 );
244 assert!(Integer::Signed(-1) < Integer::Unsigned(0));
245 assert!(Integer::Signed(i64::MAX) < Integer::Unsigned(i64::MAX.cast_unsigned() + 1));
246 assert_eq!(
247 Integer::Signed(7).cmp(&Integer::Unsigned(7)),
248 Ordering::Equal
249 );
250 }
251
252 #[test]
253 fn accessor_boundaries_match_the_spec() {
254 assert_eq!(
255 Integer::Unsigned(i64::MAX.cast_unsigned()).as_signed(),
256 Some(i64::MAX)
257 );
258 assert_eq!(
259 Integer::Unsigned(i64::MAX.cast_unsigned() + 1).as_signed(),
260 None
261 );
262 assert_eq!(Integer::Signed(-1).as_unsigned(), None);
263 assert_eq!(Integer::Signed(0).as_unsigned(), Some(0));
264 assert_eq!(Integer::Signed(i64::MIN).as_signed(), Some(i64::MIN));
265 }
266
267 #[test]
268 fn raw_parts_expose_signedness_and_bits() {
269 assert_eq!(Integer::Signed(-1).to_raw_parts(), (true, u64::MAX));
270 assert_eq!(Integer::Signed(5).to_raw_parts(), (true, 5));
271 assert_eq!(Integer::Unsigned(5).to_raw_parts(), (false, 5));
272 assert_eq!(
273 Integer::Signed(i64::MIN).to_raw_parts(),
274 (true, 0x8000_0000_0000_0000)
275 );
276 }
277
278 #[test]
279 fn display_prints_canonical_digits() {
280 assert_eq!(Integer::Signed(5).to_string(), "5");
281 assert_eq!(Integer::Unsigned(5).to_string(), "5");
282 assert_eq!(
283 Integer::Signed(-9_223_372_036_854_775_808).to_string(),
284 "-9223372036854775808"
285 );
286 assert_eq!(
287 Integer::Unsigned(u64::MAX).to_string(),
288 "18446744073709551615"
289 );
290 }
291
292 #[test]
293 fn from_impls_cover_all_eight_fixed_width_ints() {
294 assert_eq!(Integer::from(-1i8), Integer::Signed(-1));
295 assert_eq!(Integer::from(-1i16), Integer::Signed(-1));
296 assert_eq!(Integer::from(-1i32), Integer::Signed(-1));
297 assert_eq!(Integer::from(-1i64), Integer::Signed(-1));
298 assert_eq!(Integer::from(1u8), Integer::Unsigned(1));
299 assert_eq!(Integer::from(1u16), Integer::Unsigned(1));
300 assert_eq!(Integer::from(1u32), Integer::Unsigned(1));
301 assert_eq!(Integer::from(u64::MAX), Integer::Unsigned(u64::MAX));
302 }
303}