1#![cfg_attr(not(test), no_std)]
47#![deny(missing_docs)]
48
49#[cfg(doctest)]
51#[doc = include_str!("../README.md")]
52struct Readme;
53
54#[repr(transparent)]
59#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
60pub struct PackedInt {
61 inner: u16,
62}
63
64macro_rules! impl_from_type {
65 ($type:ty, $name:ident) => {
66 #[doc = concat!("Packs a [`", stringify!($type), "`], rounding up.")]
67 pub const fn $name(mut value: $type) -> Self {
71 let mut prefix = 0u16;
72
73 while value > 0x1ff {
77 prefix += 1;
78 value = (value >> 1) + (value & 1);
79 }
80
81 Self {
84 inner: (prefix << 8) + (value as u16),
85 }
86 }
87 };
88}
89
90macro_rules! impl_into_type {
91 ($type:ty, $name:ident) => {
92 #[doc = concat!("Unpacks into a [`", stringify!($type), "`], saturating at [`", stringify!($type), "::MAX`].")]
93 pub const fn $name(self) -> $type {
96 let prefix = (self.inner >> 8) as $type;
97 let suffix = (self.inner & 0xff) as $type;
98
99 if prefix == 0 {
100 suffix
101 } else if 7 + prefix >= <$type>::BITS as $type {
102 <$type>::MAX
103 } else {
104 (1 << (7 + prefix)) | (suffix << (prefix - 1))
105 }
106 }
107 };
108}
109
110macro_rules! impl_traits {
111 ($type:ty, $from:ident, $into:ident) => {
112 impl From<$type> for PackedInt {
113 fn from(value: $type) -> Self {
114 Self::$from(value)
115 }
116 }
117
118 impl From<PackedInt> for $type {
119 fn from(packed: PackedInt) -> $type {
120 packed.$into()
121 }
122 }
123 };
124}
125
126impl PackedInt {
127 pub const fn from_12_bits(bits: &[u8; 2]) -> Self {
132 Self {
133 inner: (((bits[1] & 0xf0) as u16) << 4) | (bits[0] as u16),
134 }
135 }
136
137 pub const fn to_12_bits(self) -> [u8; 2] {
147 [self.inner as u8, 0xF0 & (self.inner >> 4) as u8]
148 }
149
150 pub const fn from_16_bits(bits: &[u8; 2]) -> Self {
152 Self {
153 inner: u16::from_le_bytes(*bits),
154 }
155 }
156
157 pub const fn to_16_bits(self) -> [u8; 2] {
159 self.inner.to_le_bytes()
160 }
161
162 pub const fn from_inner_u16(inner: u16) -> Self {
167 Self { inner }
168 }
169
170 pub const fn to_inner_u16(self) -> u16 {
172 self.inner
173 }
174
175 impl_from_type!(usize, from_usize);
176 impl_from_type!(u128, from_u128);
177 impl_from_type!(u64, from_u64);
178 impl_from_type!(u32, from_u32);
179 impl_from_type!(u16, from_u16);
180
181 impl_into_type!(usize, to_usize);
182 impl_into_type!(u128, to_u128);
183 impl_into_type!(u64, to_u64);
184 impl_into_type!(u32, to_u32);
185 impl_into_type!(u16, to_u16);
186}
187
188impl_traits!(usize, from_usize, to_usize);
189impl_traits!(u128, from_u128, to_u128);
190impl_traits!(u64, from_u64, to_u64);
191impl_traits!(u32, from_u32, to_u32);
192
193#[cfg(test)]
194mod tests {
195 use crate::PackedInt;
196
197 const U128_SATURATION: u16 = 0x7900;
199
200 fn reference_value(inner: u16) -> Option<u128> {
205 let exponent = u32::from(inner >> 8);
206 let mantissa = u128::from(inner & 0xff);
207
208 if exponent == 0 {
209 return Some(mantissa);
210 }
211
212 if exponent + 7 >= u128::BITS {
213 return None;
214 }
215
216 Some((1 << (exponent + 7)) + (mantissa << (exponent - 1)))
217 }
218
219 macro_rules! assert_unpacks_or_saturates {
220 ($packed:expr, $expected:expr, $type:ty, $to:ident) => {
221 let actual = $packed.$to();
222
223 match $expected {
224 Some(value) if value <= <$type>::MAX as u128 => assert_eq!(
225 actual as u128, value,
226 concat!(stringify!($to), " of {:?} should be {}"),
227 $packed, value
228 ),
229 _ => assert_eq!(
230 actual,
231 <$type>::MAX,
232 concat!(stringify!($to), " of {:?} should saturate"),
233 $packed
234 ),
235 }
236 };
237 }
238
239 #[test]
240 fn every_representation_decodes_per_the_specification() {
241 for inner in 0..=u16::MAX {
242 let packed = PackedInt::from_inner_u16(inner);
243 let expected = reference_value(inner);
244
245 assert_unpacks_or_saturates!(packed, expected, u16, to_u16);
246 assert_unpacks_or_saturates!(packed, expected, u32, to_u32);
247 assert_unpacks_or_saturates!(packed, expected, u64, to_u64);
248 assert_unpacks_or_saturates!(packed, expected, usize, to_usize);
249
250 assert_eq!(packed.to_u128(), expected.unwrap_or(u128::MAX));
251 }
252 }
253
254 #[test]
255 fn values_increase_strictly_and_without_gaps() {
256 for inner in 0..U128_SATURATION - 1 {
257 let lower = reference_value(inner).expect("below the saturation point");
258 let upper = reference_value(inner + 1).expect("below the saturation point");
259
260 let exponent = u32::from(inner >> 8);
261 let step = 1u128 << exponent.saturating_sub(1);
262
263 assert_eq!(
264 upper - lower,
265 step,
266 "{inner:#06x} and its successor are not one step apart"
267 );
268
269 assert!(
270 PackedInt::from_inner_u16(inner) < PackedInt::from_inner_u16(inner + 1),
271 "Ord disagrees with the value order at {inner:#06x}"
272 );
273 }
274 }
275
276 #[test]
277 fn packing_returns_the_least_representable_upper_bound() {
278 assert_eq!(PackedInt::from_u128(0).to_inner_u16(), 0);
279
280 for inner in 1..U128_SATURATION {
281 let value = reference_value(inner).expect("below the saturation point");
282 let previous = reference_value(inner - 1).expect("below the saturation point");
283
284 assert_eq!(
285 PackedInt::from_u128(value).to_inner_u16(),
286 inner,
287 "{value} is representable and should pack to {inner:#06x}"
288 );
289
290 assert_eq!(
291 PackedInt::from_u128(previous + 1).to_inner_u16(),
292 inner,
293 "{} should round up to {inner:#06x}",
294 previous + 1
295 );
296 }
297 }
298
299 #[test]
300 fn packing_is_exact_below_256_and_rounds_up_above() {
301 for value in 0..256u128 {
302 assert_eq!(PackedInt::from_u128(value).to_u128(), value);
303 }
304
305 for inner in 1..U128_SATURATION {
306 let representable = reference_value(inner).expect("below the saturation point");
307
308 for value in [representable - 1, representable] {
309 let rounded = PackedInt::from_u128(value).to_u128();
310
311 assert!(rounded >= value, "{value} rounded down to {rounded}");
312
313 assert!(
314 rounded - value <= value >> 8,
315 "{value} rounded to {rounded}, further than one part in 256"
316 );
317 }
318 }
319 }
320
321 #[test]
322 fn packing_is_independent_of_the_input_width() {
323 for value in 0..=u16::MAX {
324 let packed = PackedInt::from_u16(value);
325
326 assert_eq!(PackedInt::from_u32(u32::from(value)), packed);
327 assert_eq!(PackedInt::from_u64(u64::from(value)), packed);
328 assert_eq!(PackedInt::from_u128(u128::from(value)), packed);
329 assert_eq!(PackedInt::from_usize(usize::from(value)), packed);
330 }
331 }
332
333 #[test]
334 fn packing_the_type_maximum_round_trips() {
335 assert_eq!(PackedInt::from_u16(u16::MAX).to_u16(), u16::MAX);
336 assert_eq!(PackedInt::from_u32(u32::MAX).to_u32(), u32::MAX);
337 assert_eq!(PackedInt::from_u64(u64::MAX).to_u64(), u64::MAX);
338 assert_eq!(PackedInt::from_u128(u128::MAX).to_u128(), u128::MAX);
339 assert_eq!(PackedInt::from_usize(usize::MAX).to_usize(), usize::MAX);
340 }
341
342 #[test]
343 fn powers_of_two_survive_packing() {
344 for shift in 0..u128::BITS {
345 let value = 1u128 << shift;
346
347 assert_eq!(PackedInt::from_u128(value).to_u128(), value);
348 }
349 }
350
351 #[test]
352 fn sixteen_bit_round_trip_is_lossless() {
353 for inner in 0..=u16::MAX {
354 let packed = PackedInt::from_inner_u16(inner);
355
356 assert_eq!(PackedInt::from_16_bits(&packed.to_16_bits()), packed);
357 }
358 }
359
360 #[test]
361 fn twelve_bit_round_trip_ignores_the_reserved_nibble() {
362 for inner in 0..0x1000 {
363 let packed = PackedInt::from_inner_u16(inner);
364 let bits = packed.to_12_bits();
365
366 assert_eq!(bits[1] & 0x0f, 0, "{inner:#06x} wrote the reserved nibble");
367
368 for reserved in 0..0x10 {
369 let dirty = [bits[0], bits[1] | reserved];
370
371 assert_eq!(
372 PackedInt::from_12_bits(&dirty),
373 packed,
374 "{inner:#06x} was corrupted by reserved nibble {reserved:#03x}"
375 );
376 }
377 }
378 }
379
380 #[test]
381 fn twelve_bits_hold_values_up_to_the_documented_bound() {
382 let largest = PackedInt::from_inner_u16(0x0fff);
383
384 assert_eq!(largest.to_u128(), 511 * (1 << 14));
385 assert_eq!(largest.to_u128(), 8_372_224);
386 assert_eq!(PackedInt::from_12_bits(&largest.to_12_bits()), largest);
387 }
388
389 #[test]
390 fn conversions_are_usable_in_const_context() {
391 const PACKED: PackedInt = PackedInt::from_u64(1_000_000);
392 const UNPACKED: u64 = PACKED.to_u64();
393 const BYTES: [u8; 2] = PACKED.to_16_bits();
394
395 assert_eq!(UNPACKED, 1_001_472);
396 assert_eq!(PackedInt::from_16_bits(&BYTES), PACKED);
397 }
398
399 #[test]
400 fn from_impls_agree_with_the_inherent_methods() {
401 for shift in 0..u128::BITS {
402 let value = 1u128 << shift;
403 let packed = PackedInt::from(value);
404
405 assert_eq!(packed, PackedInt::from_u128(value));
406 assert_eq!(u128::from(packed), packed.to_u128());
407 assert_eq!(u64::from(packed), packed.to_u64());
408 assert_eq!(u32::from(packed), packed.to_u32());
409 assert_eq!(usize::from(packed), packed.to_usize());
410 }
411 }
412}