Skip to main content

xlog_core/
float_order.rs

1//! Canonical IEEE 754 total-order keys for relational floating-point values.
2
3/// Convert raw `f32` bits into the unsigned key used by XLOG's total order.
4///
5/// The mapping is bijective over all bit patterns and orders values exactly as
6/// [`f32::total_cmp`]. Consequently, negative and positive zero are distinct,
7/// as are NaN sign, signaling state, and payload.
8pub const fn f32_total_order_key_from_bits(bits: u32) -> u32 {
9    let mask = if bits >> 31 == 0 {
10        0x8000_0000
11    } else {
12        u32::MAX
13    };
14    bits ^ mask
15}
16
17/// Convert an `f32` value into the unsigned key used by XLOG's total order.
18pub const fn f32_total_order_key(value: f32) -> u32 {
19    f32_total_order_key_from_bits(value.to_bits())
20}
21
22/// Convert raw `f64` bits into the unsigned key used by XLOG's total order.
23///
24/// The mapping is bijective over all bit patterns and orders values exactly as
25/// [`f64::total_cmp`]. Consequently, negative and positive zero are distinct,
26/// as are NaN sign, signaling state, and payload.
27pub const fn f64_total_order_key_from_bits(bits: u64) -> u64 {
28    let mask = if bits >> 63 == 0 {
29        0x8000_0000_0000_0000
30    } else {
31        u64::MAX
32    };
33    bits ^ mask
34}
35
36/// Convert an `f64` value into the unsigned key used by XLOG's total order.
37pub const fn f64_total_order_key(value: f64) -> u64 {
38    f64_total_order_key_from_bits(value.to_bits())
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn fixed_edge_cases_match_standard_total_order() {
47        let f32_values = [
48            f32::from_bits(0xffc0_0002),
49            f32::NEG_INFINITY,
50            -1.0,
51            -0.0,
52            0.0,
53            1.0,
54            f32::INFINITY,
55            f32::from_bits(0x7fc0_0001),
56            f32::from_bits(0x7fc0_0002),
57        ];
58        for &left in &f32_values {
59            for &right in &f32_values {
60                assert_eq!(
61                    f32_total_order_key(left).cmp(&f32_total_order_key(right)),
62                    left.total_cmp(&right)
63                );
64            }
65        }
66
67        let f64_values = [
68            f64::from_bits(0xfff8_0000_0000_0002),
69            f64::NEG_INFINITY,
70            -1.0,
71            -0.0,
72            0.0,
73            1.0,
74            f64::INFINITY,
75            f64::from_bits(0x7ff8_0000_0000_0001),
76            f64::from_bits(0x7ff8_0000_0000_0002),
77        ];
78        for &left in &f64_values {
79            for &right in &f64_values {
80                assert_eq!(
81                    f64_total_order_key(left).cmp(&f64_total_order_key(right)),
82                    left.total_cmp(&right)
83                );
84            }
85        }
86    }
87
88    #[test]
89    fn seeded_bit_patterns_match_standard_total_order() {
90        let mut left = 0x243f_6a88_85a3_08d3_u64;
91        let mut right = 0x1319_8a2e_0370_7344_u64;
92        for _ in 0..10_000 {
93            left ^= left << 13;
94            left ^= left >> 7;
95            left ^= left << 17;
96            right = right
97                .wrapping_mul(6_364_136_223_846_793_005)
98                .wrapping_add(1_442_695_040_888_963_407);
99
100            let left_f64 = f64::from_bits(left);
101            let right_f64 = f64::from_bits(right);
102            assert_eq!(
103                f64_total_order_key(left_f64).cmp(&f64_total_order_key(right_f64)),
104                left_f64.total_cmp(&right_f64)
105            );
106
107            let left_f32 = f32::from_bits(left as u32);
108            let right_f32 = f32::from_bits(right as u32);
109            assert_eq!(
110                f32_total_order_key(left_f32).cmp(&f32_total_order_key(right_f32)),
111                left_f32.total_cmp(&right_f32)
112            );
113        }
114    }
115}