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