Skip to main content

orderable_bytes/
primitive.rs

1//! Canonical, order-preserving fixed-length byte encodings for the
2//! primitives `bool`, `char`, `u8`, `i8`, `i16`, `i32`, `i64`, `u128`,
3//! `i128`, the IEEE 754 floats `f32` and `f64`, and byte arrays
4//! `[u8; N]`.
5//!
6//! Each impl emits the type's native byte width — no padding:
7//!
8//! - `bool`, `u8`, `i8` → `[u8; 1]`
9//! - `i16`, `u16` → `[u8; 2]`
10//! - `i32`, `u32`, `char`, `f32` → `[u8; 4]`
11//! - `i64`, `u64`, `f64` → `[u8; 8]`
12//! - `u128`, `i128` → `[u8; 16]`
13//! - `[u8; N]` → itself
14//!
15//! Consumers that need a fixed wider encoding (e.g. an ORE construction
16//! whose plaintext block size is `[u8; 8]`) should zero-extend the
17//! orderable bytes upstream of the encrypter; widening is monotonic on
18//! lex order so it preserves the encoding's guarantees.
19//!
20//! ## `bool`
21//!
22//! Encoded as `false → 0x00`, `true → 0x01`. Already in lex order.
23//!
24//! ## Unsigned integers (`u8`, `u16`, `u32`, `u64`, `u128`)
25//!
26//! Already in lex order — no sign-flip needed. Native big-endian.
27//!
28//! ## Signed integers (`i8`, `i16`, `i32`, `i64`, `i128`)
29//!
30//! Each two's-complement input is mapped to its unsigned equivalent by
31//! flipping the sign bit at its native width (`x ^ (1 << (N-1))`),
32//! then serialised big-endian. Sign-flipping moves negatives below
33//! positives (the sign bit `1` for negatives clears to `0`, vice versa
34//! for positives) and preserves order within each sign class.
35//!
36//! ## `char`
37//!
38//! Encoded as the big-endian bytes of the underlying `u32` Unicode
39//! scalar value (`*self as u32`). Rust's `Ord` impl for `char` compares
40//! by code point, and surrogate code points (`U+D800`..=`U+DFFF`) are
41//! not representable as `char`, so the native `u32` lex order is
42//! exactly the order we need.
43//!
44//! ## IEEE 754 floats (`f32`, `f64`)
45//!
46//! Each float is mapped to a lex-orderable unsigned integer of the
47//! same width (`u32` for `f32`, `u64` for `f64`) using the standard
48//! monotonic encoding:
49//!
50//! - Negatives flip every bit (their bit pattern's lex order is the
51//!   reverse of magnitude order, so flipping inverts it).
52//! - Positives (and `+0.0`) flip only the sign bit (bringing them above
53//!   negatives in lex order).
54//!
55//! `-0.0` is canonicalised to `+0.0` before encoding so the two compare
56//! byte-equal — matching `-0.0 == 0.0` for IEEE 754.
57//!
58//! NaN handling is unspecified. Floats implement `PartialOrd` rather
59//! than `Ord` (NaN compares unordered against every value, including
60//! itself), so the trait's order/equality guarantees only apply to
61//! non-NaN inputs. Different NaN bit patterns will produce different
62//! bytes; consumers that need a canonical NaN must canonicalise
63//! upstream.
64
65impl_fixed_orderable_bytes! {
66    bool, 1, |value| {
67        // `false as u8 == 0`, `true as u8 == 1`. `false` sorts strictly
68        // below `true`.
69        [*value as u8]
70    }
71}
72
73impl_fixed_orderable_bytes! {
74    u8, 1, |value| {
75        [*value]
76    }
77}
78
79impl_fixed_orderable_bytes! {
80    i8, 1, |value| {
81        [(*value as u8) ^ (1u8 << 7)]
82    }
83}
84
85impl_fixed_orderable_bytes! {
86    u16, 2, |value| {
87        value.to_be_bytes()
88    }
89}
90
91impl_fixed_orderable_bytes! {
92    i16, 2, |value| {
93        ((*value as u16) ^ (1u16 << 15)).to_be_bytes()
94    }
95}
96
97impl_fixed_orderable_bytes! {
98    u32, 4, |value| {
99        value.to_be_bytes()
100    }
101}
102
103impl_fixed_orderable_bytes! {
104    i32, 4, |value| {
105        ((*value as u32) ^ (1u32 << 31)).to_be_bytes()
106    }
107}
108
109impl_fixed_orderable_bytes! {
110    u64, 8, |value| {
111        value.to_be_bytes()
112    }
113}
114
115impl_fixed_orderable_bytes! {
116    i64, 8, |value| {
117        ((*value as u64) ^ (1u64 << 63)).to_be_bytes()
118    }
119}
120
121impl_fixed_orderable_bytes! {
122    u128, 16, |value| {
123        value.to_be_bytes()
124    }
125}
126
127impl_fixed_orderable_bytes! {
128    i128, 16, |value| {
129        ((*value as u128) ^ (1u128 << 127)).to_be_bytes()
130    }
131}
132
133impl_fixed_orderable_bytes! {
134    char, 4, |value| {
135        (*value as u32).to_be_bytes()
136    }
137}
138
139impl_fixed_orderable_bytes! {
140    f32, 4, |value| {
141        // Canonicalise -0.0 → 0.0 so the two share one byte encoding
142        // (their f32 equality demands byte equality under our contract).
143        let value = if *value == -0.0 { 0.0 } else { *value };
144        let bits = value.to_bits();
145        // Branchless monotonic mapping (see `f64` impl for derivation).
146        let sign_extension = (bits as i32 >> 31) as u32;
147        let mask = sign_extension | (1u32 << 31);
148        (bits ^ mask).to_be_bytes()
149    }
150}
151
152impl_fixed_orderable_bytes! {
153    f64, 8, |value| {
154        // Canonicalise -0.0 → 0.0 so the two share one byte encoding
155        // (their f64 equality demands byte equality under our contract).
156        let value = if *value == -0.0 { 0.0 } else { *value };
157        let bits = value.to_bits();
158        // Branchless monotonic mapping. `sign_extension` is `u64::MAX`
159        // when the input is negative (sign bit `1`) and `0` when
160        // positive. ORing in `1 << 63` makes the mask `u64::MAX` for
161        // negatives (XOR-flip every bit) and `1 << 63` for positives
162        // (XOR-flip just the sign bit).
163        let sign_extension = (bits as i64 >> 63) as u64;
164        let mask = sign_extension | (1u64 << 63);
165        (bits ^ mask).to_be_bytes()
166    }
167}
168
169impl<const N: usize> crate::private::Sealed for [u8; N] {}
170
171/// A byte array encodes to itself: byte-wise order is already the order
172/// Rust defines for arrays. Hashes and UUIDs can be encoded without
173/// copying them into a `Vec<u8>`.
174impl<const N: usize> crate::OrderableBytes for [u8; N] {
175    type Bytes<'a> = &'a [u8];
176
177    fn to_orderable_bytes(&self) -> &[u8] {
178        self
179    }
180}
181
182impl<const N: usize> crate::FixedOrderableBytes for [u8; N] {
183    const ENCODED_LEN: usize = N;
184
185    type Array = [u8; N];
186
187    fn to_fixed_orderable_bytes(&self) -> [u8; N] {
188        *self
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use crate::{FixedOrderableBytes, OrderableBytes};
195
196    // --- [u8; N] ---
197
198    #[test]
199    fn byte_array_encodes_to_itself() {
200        let a = [1u8, 2, 3];
201        assert_eq!(a.to_orderable_bytes(), &a);
202        assert_eq!(a.to_orderable_bytes().as_ptr(), a.as_ptr());
203        assert_eq!(a.to_fixed_orderable_bytes(), a);
204        assert_eq!(<[u8; 3] as FixedOrderableBytes>::ENCODED_LEN, 3);
205    }
206
207    quickcheck! {
208        fn byte_array_order_matches_array_order(a: Vec<u8>, b: Vec<u8>) -> bool {
209            let mut x = [0u8; 8];
210            let mut y = [0u8; 8];
211            for (dst, src) in x.iter_mut().zip(&a) { *dst = *src; }
212            for (dst, src) in y.iter_mut().zip(&b) { *dst = *src; }
213            x.to_orderable_bytes().cmp(y.to_orderable_bytes()) == x.cmp(&y)
214        }
215    }
216
217    // --- bool ---
218
219    #[test]
220    fn bool_known_anchors() {
221        assert_eq!(false.to_orderable_bytes(), [0x00]);
222        assert_eq!(true.to_orderable_bytes(), [0x01]);
223    }
224
225    #[test]
226    fn bool_byte_order_matches_natural_order() {
227        assert!(false.to_orderable_bytes() < true.to_orderable_bytes());
228    }
229
230    // --- u8 ---
231
232    #[test]
233    fn u8_known_anchors() {
234        // Native: u8 is already in lex order, no transform.
235        assert_eq!(u8::MIN.to_orderable_bytes(), [0x00]);
236        assert_eq!(0x42u8.to_orderable_bytes(), [0x42]);
237        assert_eq!(u8::MAX.to_orderable_bytes(), [0xFF]);
238    }
239
240    #[test]
241    fn u8_byte_order_matches_natural_order() {
242        let ascending = [u8::MIN, 1, 100, 200, u8::MAX];
243        for window in ascending.windows(2) {
244            assert!(
245                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
246                "{} < {} failed",
247                window[0],
248                window[1]
249            );
250        }
251    }
252
253    // --- i8 ---
254
255    #[test]
256    fn i8_known_anchors() {
257        // Sign-flip at u8 (XOR 0x80) so MIN→0x00, 0→0x80, MAX→0xFF.
258        assert_eq!(i8::MIN.to_orderable_bytes(), [0x00]);
259        assert_eq!(0i8.to_orderable_bytes(), [0x80]);
260        assert_eq!(i8::MAX.to_orderable_bytes(), [0xFF]);
261    }
262
263    #[test]
264    fn i8_byte_order_matches_natural_order() {
265        let ascending = [i8::MIN, -100, -1, 0, 1, 100, i8::MAX];
266        for window in ascending.windows(2) {
267            assert!(
268                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
269                "{} < {} failed",
270                window[0],
271                window[1]
272            );
273        }
274    }
275
276    // --- u16 ---
277
278    #[test]
279    fn u16_known_anchors() {
280        assert_eq!(u16::MIN.to_orderable_bytes(), [0x00, 0x00]);
281        assert_eq!(u16::MAX.to_orderable_bytes(), [0xFF, 0xFF]);
282        assert_eq!(0x1234u16.to_orderable_bytes(), [0x12, 0x34]);
283    }
284
285    #[test]
286    fn u16_byte_order_matches_natural_order() {
287        let ascending = [u16::MIN, 1, 256, 10000, u16::MAX - 1, u16::MAX];
288        for window in ascending.windows(2) {
289            assert!(
290                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
291                "{} < {} failed",
292                window[0],
293                window[1]
294            );
295        }
296    }
297
298    // --- i16 ---
299
300    #[test]
301    fn i16_known_anchors() {
302        // Sign-flip at u16 (XOR 0x8000), then BE.
303        assert_eq!(i16::MIN.to_orderable_bytes(), [0x00, 0x00]);
304        assert_eq!(0i16.to_orderable_bytes(), [0x80, 0x00]);
305        assert_eq!(i16::MAX.to_orderable_bytes(), [0xFF, 0xFF]);
306    }
307
308    #[test]
309    fn i16_byte_order_matches_natural_order() {
310        let ascending = [i16::MIN, -10000, -1, 0, 1, 10000, i16::MAX];
311        for window in ascending.windows(2) {
312            assert!(
313                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
314                "{} < {} failed",
315                window[0],
316                window[1]
317            );
318        }
319    }
320
321    // --- u32 ---
322
323    #[test]
324    fn u32_known_anchors() {
325        assert_eq!(u32::MIN.to_orderable_bytes(), [0x00; 4]);
326        assert_eq!(u32::MAX.to_orderable_bytes(), [0xFF; 4]);
327        assert_eq!(
328            0x1234_5678u32.to_orderable_bytes(),
329            [0x12, 0x34, 0x56, 0x78]
330        );
331    }
332
333    #[test]
334    fn u32_byte_order_matches_natural_order() {
335        let ascending = [
336            u32::MIN,
337            1,
338            1 << 8,
339            1 << 16,
340            1 << 24,
341            u32::MAX - 1,
342            u32::MAX,
343        ];
344        for window in ascending.windows(2) {
345            assert!(
346                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
347                "{} < {} failed",
348                window[0],
349                window[1]
350            );
351        }
352    }
353
354    // --- i32 ---
355
356    #[test]
357    fn i32_known_anchors() {
358        // Sign-flip at u32 (XOR 0x8000_0000), then BE.
359        assert_eq!(i32::MIN.to_orderable_bytes(), [0x00, 0x00, 0x00, 0x00]);
360        assert_eq!(0i32.to_orderable_bytes(), [0x80, 0x00, 0x00, 0x00]);
361        assert_eq!(i32::MAX.to_orderable_bytes(), [0xFF, 0xFF, 0xFF, 0xFF]);
362    }
363
364    #[test]
365    fn i32_byte_order_matches_natural_order() {
366        let ascending = [i32::MIN, -1_000_000_000, -1, 0, 1, 1_000_000_000, i32::MAX];
367        for window in ascending.windows(2) {
368            assert!(
369                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
370                "{} < {} failed",
371                window[0],
372                window[1]
373            );
374        }
375    }
376
377    // --- u64 ---
378
379    #[test]
380    fn u64_known_anchors() {
381        assert_eq!(u64::MIN.to_orderable_bytes(), [0x00; 8]);
382        assert_eq!(u64::MAX.to_orderable_bytes(), [0xFF; 8]);
383        let one = 1u64.to_orderable_bytes();
384        let mut expected_one = [0u8; 8];
385        expected_one[7] = 1;
386        assert_eq!(one, expected_one);
387    }
388
389    #[test]
390    fn u64_byte_order_matches_natural_order() {
391        let ascending = [
392            u64::MIN,
393            1,
394            1 << 16,
395            1 << 32,
396            1 << 48,
397            u64::MAX - 1,
398            u64::MAX,
399        ];
400        for window in ascending.windows(2) {
401            assert!(
402                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
403                "{} < {} failed",
404                window[0],
405                window[1]
406            );
407        }
408    }
409
410    // --- i64 ---
411
412    #[test]
413    fn i64_known_anchors() {
414        assert_eq!(i64::MIN.to_orderable_bytes(), [0x00; 8]);
415        assert_eq!(
416            0i64.to_orderable_bytes(),
417            [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
418        );
419        assert_eq!(i64::MAX.to_orderable_bytes(), [0xFF; 8]);
420    }
421
422    #[test]
423    fn i64_byte_order_matches_natural_order() {
424        let ascending = [
425            i64::MIN,
426            -1_000_000_000_000,
427            -1,
428            0,
429            1,
430            1_000_000_000_000,
431            i64::MAX,
432        ];
433        for window in ascending.windows(2) {
434            assert!(
435                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
436                "{} < {} failed",
437                window[0],
438                window[1]
439            );
440        }
441    }
442
443    // --- u128 ---
444
445    #[test]
446    fn u128_known_anchors() {
447        assert_eq!(u128::MIN.to_orderable_bytes(), [0; 16]);
448        assert_eq!(u128::MAX.to_orderable_bytes(), [0xFF; 16]);
449        let one = 1u128.to_orderable_bytes();
450        let mut expected_one = [0u8; 16];
451        expected_one[15] = 1;
452        assert_eq!(one, expected_one);
453    }
454
455    #[test]
456    fn u128_byte_order_matches_natural_order() {
457        let ascending = [
458            u128::MIN,
459            1,
460            (1u128 << 32),
461            (1u128 << 64),
462            (1u128 << 96),
463            u128::MAX - 1,
464            u128::MAX,
465        ];
466        for window in ascending.windows(2) {
467            assert!(
468                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
469                "{} < {} failed",
470                window[0],
471                window[1]
472            );
473        }
474    }
475
476    // --- i128 ---
477
478    #[test]
479    fn i128_known_anchors() {
480        assert_eq!(i128::MIN.to_orderable_bytes(), [0; 16]);
481        assert_eq!(i128::MAX.to_orderable_bytes(), [0xFF; 16]);
482        let mut expected_zero = [0u8; 16];
483        expected_zero[0] = 0x80;
484        assert_eq!(0i128.to_orderable_bytes(), expected_zero);
485    }
486
487    #[test]
488    fn i128_byte_order_matches_natural_order() {
489        let ascending = [
490            i128::MIN,
491            -(1i128 << 96),
492            -(1i128 << 64),
493            -1,
494            0,
495            1,
496            (1i128 << 64),
497            (1i128 << 96),
498            i128::MAX,
499        ];
500        for window in ascending.windows(2) {
501            assert!(
502                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
503                "{} < {} failed",
504                window[0],
505                window[1]
506            );
507        }
508    }
509
510    // --- char ---
511
512    #[test]
513    fn char_known_anchors() {
514        // 'A' = U+0041 = 0x0000_0041 BE.
515        assert_eq!('A'.to_orderable_bytes(), [0x00, 0x00, 0x00, 0x41]);
516        // '\0' = U+0000 (lowest code point).
517        assert_eq!('\0'.to_orderable_bytes(), [0x00, 0x00, 0x00, 0x00]);
518        // char::MAX = U+10FFFF (highest valid scalar value).
519        assert_eq!(char::MAX.to_orderable_bytes(), [0x00, 0x10, 0xFF, 0xFF]);
520    }
521
522    #[test]
523    fn char_byte_order_matches_natural_order() {
524        // Spans ASCII, BMP, and supplementary planes (above the surrogate gap).
525        let ascending = [
526            '\0',
527            '0',
528            'A',
529            'a',
530            '\u{7F}',
531            '\u{D7FF}',
532            '\u{E000}',
533            '\u{1F600}',
534            char::MAX,
535        ];
536        for window in ascending.windows(2) {
537            assert!(
538                window[0].to_orderable_bytes() < window[1].to_orderable_bytes(),
539                "{:?} < {:?} failed",
540                window[0],
541                window[1]
542            );
543        }
544    }
545
546    // --- f32 ---
547
548    #[test]
549    fn f32_zero_canonical_bytes() {
550        // +0.0 → 0x8000_0000 (sign-bit-only flip on all-zero bits).
551        assert_eq!(0.0f32.to_orderable_bytes(), [0x80, 0x00, 0x00, 0x00]);
552    }
553
554    #[test]
555    fn f32_negative_zero_canonicalises_with_zero() {
556        assert_eq!((-0.0f32).to_orderable_bytes(), 0.0f32.to_orderable_bytes());
557    }
558
559    #[test]
560    fn f32_byte_order_matches_natural_order() {
561        let ascending = [
562            f32::NEG_INFINITY,
563            f32::MIN,
564            -1e30,
565            -1.0,
566            -f32::MIN_POSITIVE,
567            0.0,
568            f32::MIN_POSITIVE,
569            1.0,
570            1e30,
571            f32::MAX,
572            f32::INFINITY,
573        ];
574        for window in ascending.windows(2) {
575            let a = window[0].to_orderable_bytes();
576            let b = window[1].to_orderable_bytes();
577            assert!(a < b, "{} < {} failed", window[0], window[1]);
578        }
579    }
580
581    #[test]
582    fn f32_subnormals_sort_above_zero_below_normals() {
583        // Smallest positive subnormal (`f32::from_bits(1)`) must land
584        // strictly between 0.0 and the smallest positive normal.
585        let subnormal = f32::from_bits(1);
586        assert!(0.0f32.to_orderable_bytes() < subnormal.to_orderable_bytes());
587        assert!(subnormal.to_orderable_bytes() < f32::MIN_POSITIVE.to_orderable_bytes());
588    }
589
590    // --- f64 ---
591
592    #[test]
593    fn f64_zero_canonical_bytes() {
594        // +0.0 → 0x8000_0000_0000_0000 (sign-bit-only flip on all-zero bits).
595        assert_eq!(
596            0.0f64.to_orderable_bytes(),
597            [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
598        );
599    }
600
601    #[test]
602    fn f64_negative_zero_canonicalises_with_zero() {
603        assert_eq!((-0.0f64).to_orderable_bytes(), 0.0f64.to_orderable_bytes());
604    }
605
606    #[test]
607    fn f64_byte_order_matches_natural_order() {
608        let ascending = [
609            f64::NEG_INFINITY,
610            f64::MIN,
611            -1e100,
612            -1.0,
613            -f64::MIN_POSITIVE,
614            0.0,
615            f64::MIN_POSITIVE,
616            1.0,
617            1e100,
618            f64::MAX,
619            f64::INFINITY,
620        ];
621        for window in ascending.windows(2) {
622            let a = window[0].to_orderable_bytes();
623            let b = window[1].to_orderable_bytes();
624            assert!(a < b, "{} < {} failed", window[0], window[1]);
625        }
626    }
627
628    #[test]
629    fn f64_subnormals_sort_above_zero_below_normals() {
630        // Smallest positive subnormal (`f64::from_bits(1)`) must land
631        // strictly between 0.0 and the smallest positive normal.
632        let subnormal = f64::from_bits(1);
633        assert!(0.0f64.to_orderable_bytes() < subnormal.to_orderable_bytes());
634        assert!(subnormal.to_orderable_bytes() < f64::MIN_POSITIVE.to_orderable_bytes());
635    }
636}