From 3443ac2140999664801f20b80f82c3750ad0f0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=88=CE=BB=CE=BB=CE=B5=CE=BD=20=CE=95=CE=BC=CE=AF=CE=BB?= =?UTF-8?q?=CE=B9=CE=B1=20=CE=86=CE=BD=CE=BD=CE=B1=20Zscheile?= Date: Sat, 15 Aug 2026 20:18:44 +0200 Subject: [PATCH] Introduce trait for (forced) total ordering of numbers --- src/float.rs | 85 +------------------- src/ops/mod.rs | 1 + src/ops/total_order.rs | 178 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 83 deletions(-) create mode 100644 src/ops/total_order.rs diff --git a/src/float.rs b/src/float.rs index 4124e92c..086552be 100644 --- a/src/float.rs +++ b/src/float.rs @@ -1,4 +1,3 @@ -use core::cmp::Ordering; use core::num::FpCategory; use core::ops::{Add, Div, Neg}; @@ -2242,88 +2241,8 @@ float_const_impl! { SQRT_2, } -/// Trait for floating point numbers that provide an implementation -/// of the `totalOrder` predicate as defined in the IEEE 754 (2008 revision) -/// floating point standard. -pub trait TotalOrder { - /// Return the ordering between `self` and `other`. - /// - /// Unlike the standard partial comparison between floating point numbers, - /// this comparison always produces an ordering in accordance to - /// the `totalOrder` predicate as defined in the IEEE 754 (2008 revision) - /// floating point standard. The values are ordered in the following sequence: - /// - /// - negative quiet NaN - /// - negative signaling NaN - /// - negative infinity - /// - negative numbers - /// - negative subnormal numbers - /// - negative zero - /// - positive zero - /// - positive subnormal numbers - /// - positive numbers - /// - positive infinity - /// - positive signaling NaN - /// - positive quiet NaN. - /// - /// The ordering established by this function does not always agree with the - /// [`PartialOrd`] and [`PartialEq`] implementations. For example, - /// they consider negative and positive zero equal, while `total_cmp` - /// doesn't. - /// - /// The interpretation of the signaling NaN bit follows the definition in - /// the IEEE 754 standard, which may not match the interpretation by some of - /// the older, non-conformant (e.g. MIPS) hardware implementations. - /// - /// # Examples - /// ``` - /// use num_traits::float::TotalOrder; - /// use std::cmp::Ordering; - /// use std::{f32, f64}; - /// - /// fn check_eq(x: T, y: T) { - /// assert_eq!(x.total_cmp(&y), Ordering::Equal); - /// } - /// - /// check_eq(f64::NAN, f64::NAN); - /// check_eq(f32::NAN, f32::NAN); - /// - /// fn check_lt(x: T, y: T) { - /// assert_eq!(x.total_cmp(&y), Ordering::Less); - /// } - /// - /// check_lt(-f64::NAN, f64::NAN); - /// check_lt(f64::INFINITY, f64::NAN); - /// check_lt(-0.0_f64, 0.0_f64); - /// ``` - fn total_cmp(&self, other: &Self) -> Ordering; -} -macro_rules! totalorder_impl { - ($T:ident, $I:ident, $U:ident, $bits:expr) => { - impl TotalOrder for $T { - #[inline] - #[cfg(has_total_cmp)] - fn total_cmp(&self, other: &Self) -> Ordering { - // Forward to the core implementation - Self::total_cmp(&self, other) - } - #[inline] - #[cfg(not(has_total_cmp))] - fn total_cmp(&self, other: &Self) -> Ordering { - // Backport the core implementation (since 1.62) - let mut left = self.to_bits() as $I; - let mut right = other.to_bits() as $I; - - left ^= (((left >> ($bits - 1)) as $U) >> 1) as $I; - right ^= (((right >> ($bits - 1)) as $U) >> 1) as $I; - - left.cmp(&right) - } - } - }; -} -totalorder_impl!(f64, i64, u64, 64); -totalorder_impl!(f32, i32, u32, 32); +#[deprecated(since = "0.2.20", note = "use ops::total_order::TotalOrder instead")] +pub use crate::ops::total_order::TotalOrder; #[cfg(test)] mod tests { diff --git a/src/ops/mod.rs b/src/ops/mod.rs index 2128d86a..16b1ba09 100644 --- a/src/ops/mod.rs +++ b/src/ops/mod.rs @@ -5,4 +5,5 @@ pub mod inv; pub mod mul_add; pub mod overflowing; pub mod saturating; +pub mod total_order; pub mod wrapping; diff --git a/src/ops/total_order.rs b/src/ops/total_order.rs new file mode 100644 index 00000000..60bd4590 --- /dev/null +++ b/src/ops/total_order.rs @@ -0,0 +1,178 @@ +#![forbid(unconditional_recursion)] + +use core::{ + cmp::Ordering, + num::{NonZero, self}, +}; + +/// Returns the ordering between `self` and `other`. +/// +/// For some types (particularly floating-point types), there exists a way to +/// induce a canonical ordering on them, but as it might behave in unexpected +/// ways, it isn't the default. +/// +/// # Integers +/// +/// For integers, this just forwards to the normal [`Ord`] implementation. +/// +/// # Floating point numbers +/// +/// For floating point numbers this provides an implementation +/// of the `totalOrder` predicate as defined in the IEEE 754 (2008 revision) +/// floating point standard. +pub trait TotalOrder { + /// Return the ordering between `self` and `other`. + /// + /// # Floating point numbers + /// + /// Unlike the standard partial comparison between floating point numbers, + /// this comparison always produces an ordering in accordance to + /// the `totalOrder` predicate as defined in the IEEE 754 (2008 revision) + /// floating point standard. The values are ordered in the following sequence: + /// + /// - negative quiet NaN + /// - negative signaling NaN + /// - negative infinity + /// - negative numbers + /// - negative subnormal numbers + /// - negative zero + /// - positive zero + /// - positive subnormal numbers + /// - positive numbers + /// - positive infinity + /// - positive signaling NaN + /// - positive quiet NaN. + /// + /// The ordering established by this function does not always agree with the + /// [`PartialOrd`] and [`PartialEq`] implementations. For example, + /// they consider negative and positive zero equal, while `total_cmp` + /// doesn't. + /// + /// The interpretation of the signaling NaN bit follows the definition in + /// the IEEE 754 standard, which may not match the interpretation by some of + /// the older, non-conformant (e.g. MIPS) hardware implementations. + /// + /// # Examples + /// ``` + /// use num_traits::ops::total_order::TotalOrder; + /// use std::cmp::Ordering; + /// use std::{f32, f64}; + /// + /// fn check_eq(x: T, y: T) { + /// assert_eq!(x.total_cmp(&y), Ordering::Equal); + /// } + /// + /// check_eq(f64::NAN, f64::NAN); + /// check_eq(f32::NAN, f32::NAN); + /// + /// fn check_lt(x: T, y: T) { + /// assert_eq!(x.total_cmp(&y), Ordering::Less); + /// } + /// + /// check_lt(-f64::NAN, f64::NAN); + /// check_lt(f64::INFINITY, f64::NAN); + /// check_lt(-0.0_f64, 0.0_f64); + /// ``` + fn total_cmp(&self, other: &Self) -> Ordering; +} + +impl TotalOrder for &T { + #[inline] + fn total_cmp(&self, other: &Self) -> Ordering { + (*self).total_cmp(*other) + } +} + +impl TotalOrder for &mut T { + #[inline] + fn total_cmp(&self, other: &Self) -> Ordering { + (**self).total_cmp(&**other) + } +} + +impl TotalOrder for Option { + fn total_cmp(&self, other: &Self) -> Ordering { + // Implemented the same as `Ord for Option<_>` + match (self, other) { + (Some(l), Some(r)) => l.total_cmp(r), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, + } + } +} + +#[cfg(has_num_saturating)] +impl TotalOrder for num::Saturating { + #[inline] + fn total_cmp(&self, other: &Self) -> Ordering { + self.0.total_cmp(&other.0) + } +} + +impl TotalOrder for num::Wrapping { + #[inline] + fn total_cmp(&self, other: &Self) -> Ordering { + self.0.total_cmp(&other.0) + } +} + +macro_rules! totalorder_float_impl { + ($T:ident, $I:ident, $U:ident, $bits:expr) => { + impl TotalOrder for $T { + #[inline] + #[cfg(has_total_cmp)] + fn total_cmp(&self, other: &Self) -> Ordering { + // Forward to the core implementation + Self::total_cmp(&self, other) + } + #[inline] + #[cfg(not(has_total_cmp))] + fn total_cmp(&self, other: &Self) -> Ordering { + // Backport the core implementation (since 1.62) + let mut left = self.to_bits() as $I; + let mut right = other.to_bits() as $I; + + left ^= (((left >> ($bits - 1)) as $U) >> 1) as $I; + right ^= (((right >> ($bits - 1)) as $U) >> 1) as $I; + + left.cmp(&right) + } + } + }; +} +totalorder_float_impl!(f64, i64, u64, 64); +totalorder_float_impl!(f32, i32, u32, 32); + +macro_rules! totalorder_impl_via_ord_core { + ($($t:ty),* $(,)?) => { + $( + impl TotalOrder for $t { + #[inline] + fn total_cmp(&self, other: &Self) -> Ordering { + Self::cmp(self, other) + } + } + )* + } +} + +totalorder_impl_via_ord_core!((), bool, Ordering); + +macro_rules! totalorder_impl_zeroable_via_ord_core { + ($($t:ty),* $(,)?) => { + totalorder_impl_via_ord_core!($($t,)*); + $( + impl TotalOrder for NonZero<$t> { + #[inline] + fn total_cmp(&self, other: &Self) -> Ordering { + <$t>::cmp(&self.get(), &other.get()) + } + } + )* + } +} + +totalorder_impl_zeroable_via_ord_core!( + char, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize, +);