ps-pint16
Packs unsigned integers into a u16 via variable precision.
A PackedInt keeps nine significant bits and a scale, so any value up to
511 × 2²⁵⁴ fits in two bytes. Packing is lossy above 255: the value is rounded
up to the nearest representable one, never by more than one part in 256.
use PackedInt;
// Values below 256 survive exactly.
assert_eq!;
// Larger ones round up to the nearest representable value.
assert_eq!;
// Two bytes, whatever the width of the input.
assert_eq!;
Encoding
The high byte of the representation is an exponent e, the low byte a
mantissa m:
| exponent | value | range | step |
|---|---|---|---|
0 |
m |
0 ..= 255 |
1 |
e ≥ 1 |
2ᵉ⁺⁷ + m × 2ᵉ⁻¹ |
2ᵉ⁺⁷ ..= 511 × 2ᵉ⁻¹ |
2ᵉ⁻¹ |
Consecutive exponents meet exactly one step apart, so the 65 536 representations form a strictly increasing sequence with no gaps and no duplicates. Two consequences follow:
- Every
u16is a validPackedInt, sofrom_inner_u16cannot fail. - The derived
Ordagrees with the order of the values represented, so packed integers can be sorted and compared without unpacking.
Rounding and saturation
Packing rounds up, so unpacking never returns less than was packed. When a packed value exceeds the target type, unpacking saturates at that type's maximum instead of wrapping. Both directions are total: no input panics.
Storage
to_16_bits/from_16_bitsstore the full range as two little-endian bytes.to_12_bits/from_12_bitsuse all of the first byte and the high nibble of the second, leaving the low nibble free for the caller. Twelve bits hold only the low nibble of the exponent, so this form is lossless for values up to511 × 2¹⁴(8 372 224) and no further.
Compatibility
#![no_std], no dependencies, every conversion is a const fn. Builds on Rust
1.58 and later.
License
GPL-3.0-or-later