zerodds_float128/cmp.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Comparison: the IEEE one, and the total one a key needs.
4
5use core::cmp::Ordering;
6
7use crate::float::{Float128, SIGN_MASK};
8
9impl PartialEq for Float128 {
10 /// IEEE 754-2008 clause 5.11 `compareQuietEqual`.
11 ///
12 /// `-0.0 == 0.0`, and a NaN is equal to nothing, itself included.
13 /// The same contract `f64` has, so a generated structure holding a
14 /// `Float128` compares the way the same structure holding a `double`
15 /// does — a binding whose equality changed with the member's width
16 /// would be a binding that fails a round trip test for a reason that
17 /// has nothing to do with the wire.
18 fn eq(&self, other: &Self) -> bool {
19 if self.is_nan() || other.is_nan() {
20 return false;
21 }
22 if self.is_zero() && other.is_zero() {
23 return true;
24 }
25 self.to_bits() == other.to_bits()
26 }
27}
28
29impl PartialOrd for Float128 {
30 /// IEEE 754-2008 clause 5.11. `None` when either side is a NaN, the
31 /// unordered case.
32 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33 if self.is_nan() || other.is_nan() {
34 return None;
35 }
36 if self.is_zero() && other.is_zero() {
37 return Some(Ordering::Equal);
38 }
39 Some(ordered_key(*self).cmp(&ordered_key(*other)))
40 }
41}
42
43impl Float128 {
44 /// IEEE 754-2008 clause 5.10 `totalOrder`.
45 ///
46 /// A total order over every bit pattern: `-NaN < -inf < … < -0.0 <
47 /// +0.0 < … < +inf < +NaN`, with NaNs ordered by payload. This is
48 /// what a `@key` member and a sorted expectation need — `partial_cmp`
49 /// returning `None` for a NaN key would leave the sort order to
50 /// whatever the caller's comparator does with `None`, which is how
51 /// two peers disagree about instance order for a sample neither
52 /// considers unusual.
53 ///
54 /// The same construction `f64::total_cmp` uses: read the pattern as
55 /// a sign-magnitude integer and map it onto a two's-complement one.
56 #[must_use]
57 pub fn total_cmp(self, other: Self) -> Ordering {
58 ordered_key(self).cmp(&ordered_key(other))
59 }
60
61 /// The larger of two values under [`Self::total_cmp`].
62 #[must_use]
63 pub fn total_max(self, other: Self) -> Self {
64 if self.total_cmp(other) == Ordering::Less {
65 other
66 } else {
67 self
68 }
69 }
70
71 /// The smaller of two values under [`Self::total_cmp`].
72 #[must_use]
73 pub fn total_min(self, other: Self) -> Self {
74 if self.total_cmp(other) == Ordering::Greater {
75 other
76 } else {
77 self
78 }
79 }
80}
81
82/// The bit pattern mapped so that unsigned integer order *is* numeric
83/// order.
84///
85/// A negative value's pattern counts the wrong way round — a larger
86/// magnitude is a larger unsigned pattern but a smaller number — so the
87/// non-sign bits are complemented for negatives and the sign bit is set
88/// for positives.
89fn ordered_key(value: Float128) -> u128 {
90 let bits = value.to_bits();
91 if bits & SIGN_MASK == 0 {
92 bits | SIGN_MASK
93 } else {
94 !bits
95 }
96}