Skip to content

Commit 2f00cfa

Browse files
TotalOrder trait for floating point numbers
Define an orthogonal trait which corresponds to the `totalOrder` predicate the IEEE 754 (2008 revision) floating point standard. In order to maintain coherence, the bounds on `TotalOrder` should most likely be `TotalOrder: Float` (or `TotalOrder: FloatCore`). Without type constraints, `TotalOrder` could be defined on, well, anything. Though slightly ugly, one way to deal with this is to define two traits, `TotalOrderCore: FloatCore` and `TotalOrder: Float`. On the other hand, `Inv` has no such constraints (due to the possibility of a meaningful implementation on rational numbers).
1 parent 0a27d8c commit 2f00cfa

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

src/float.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,74 @@ float_const_impl! {
22102210
SQRT_2,
22112211
}
22122212

2213+
/// Trait for floating point numbers that provide an implementation
2214+
/// of the `totalOrder` predicate as defined in the IEEE 754 (2008 revision)
2215+
/// floating point standard.
2216+
pub trait TotalOrder {
2217+
/// Return the ordering between `self` and `other`.
2218+
///
2219+
/// Unlike the standard partial comparison between floating point numbers,
2220+
/// this comparison always produces an ordering in accordance to
2221+
/// the `totalOrder` predicate as defined in the IEEE 754 (2008 revision)
2222+
/// floating point standard. The values are ordered in the following sequence:
2223+
///
2224+
/// - negative quiet NaN
2225+
/// - negative signaling NaN
2226+
/// - negative infinity
2227+
/// - negative numbers
2228+
/// - negative subnormal numbers
2229+
/// - negative zero
2230+
/// - positive zero
2231+
/// - positive subnormal numbers
2232+
/// - positive numbers
2233+
/// - positive infinity
2234+
/// - positive signaling NaN
2235+
/// - positive quiet NaN.
2236+
///
2237+
/// The ordering established by this function does not always agree with the
2238+
/// [`PartialOrd`] and [`PartialEq`] implementations. For example,
2239+
/// they consider negative and positive zero equal, while `total_cmp`
2240+
/// doesn't.
2241+
///
2242+
/// The interpretation of the signaling NaN bit follows the definition in
2243+
/// the IEEE 754 standard, which may not match the interpretation by some of
2244+
/// the older, non-conformant (e.g. MIPS) hardware implementations.
2245+
///
2246+
/// # Examples
2247+
/// ```
2248+
/// use num_traits::float::TotalOrder;
2249+
/// use std::{f32, f64};
2250+
///
2251+
/// fn check_eq<T: TotalOrder>(x: T, y: T) {
2252+
/// assert_eq!(x.total_cmp(&y), std::cmp::Ordering::Equal);
2253+
/// }
2254+
///
2255+
/// check_eq(f64::NAN, f64::NAN);
2256+
/// check_eq(f32::NAN, f32::NAN);
2257+
///
2258+
/// fn check_lt<T: TotalOrder>(x: T, y: T) {
2259+
/// assert_eq!(x.total_cmp(&y), std::cmp::Ordering::Less);
2260+
/// }
2261+
///
2262+
/// check_lt(-f64::NAN, f64::NAN);
2263+
/// check_lt(f64::INFINITY, f64::NAN);
2264+
/// check_lt(-0.0_f64, 0.0_f64);
2265+
/// ```
2266+
fn total_cmp(&self, other: &Self) -> std::cmp::Ordering;
2267+
}
2268+
macro_rules! totalorder_impl {
2269+
($T:ident) => {
2270+
impl TotalOrder for $T {
2271+
#[inline]
2272+
fn total_cmp(&self, other: &Self) -> std::cmp::Ordering {
2273+
Self::total_cmp(&self, other)
2274+
}
2275+
}
2276+
};
2277+
}
2278+
totalorder_impl!(f64);
2279+
totalorder_impl!(f32);
2280+
22132281
#[cfg(test)]
22142282
mod tests {
22152283
use core::f64::consts;
@@ -2341,4 +2409,50 @@ mod tests {
23412409
test_subnormal::<f64>();
23422410
test_subnormal::<f32>();
23432411
}
2412+
2413+
#[test]
2414+
fn total_cmp() {
2415+
use crate::float::{Float, TotalOrder};
2416+
fn check_eq<T: Float + TotalOrder>(x: T, y: T) {
2417+
assert_eq!(x.total_cmp(&y), std::cmp::Ordering::Equal);
2418+
}
2419+
fn check_lt<T: Float + TotalOrder>(x: T, y: T) {
2420+
assert_eq!(x.total_cmp(&y), std::cmp::Ordering::Less);
2421+
}
2422+
fn check_gt<T: Float + TotalOrder>(x: T, y: T) {
2423+
assert_eq!(x.total_cmp(&y), std::cmp::Ordering::Greater);
2424+
}
2425+
2426+
check_eq(f64::NAN, f64::NAN);
2427+
check_eq(f32::NAN, f32::NAN);
2428+
2429+
check_lt(-0.0_f64, 0.0_f64);
2430+
check_lt(-0.0_f32, 0.0_f32);
2431+
2432+
let s_nan = unsafe { std::mem::transmute::<u64, f64>(0x7ff4000000000000) };
2433+
let q_nan = unsafe { std::mem::transmute::<u64, f64>(0x7ff8000000000000) };
2434+
check_lt(s_nan, q_nan);
2435+
2436+
let neg_s_nan = unsafe { std::mem::transmute::<u64, f64>(0xfff4000000000000) };
2437+
let neg_q_nan = unsafe { std::mem::transmute::<u64, f64>(0xfff8000000000000) };
2438+
check_lt(neg_q_nan, neg_s_nan);
2439+
2440+
let s_nan = unsafe { std::mem::transmute::<u32, f32>(0x7fa00000) };
2441+
let q_nan = unsafe { std::mem::transmute::<u32, f32>(0x7fc00000) };
2442+
check_lt(s_nan, q_nan);
2443+
2444+
let neg_s_nan = unsafe { std::mem::transmute::<u32, f32>(0xffa00000) };
2445+
let neg_q_nan = unsafe { std::mem::transmute::<u32, f32>(0xffc00000) };
2446+
check_lt(neg_q_nan, neg_s_nan);
2447+
2448+
check_lt(-f64::NAN, f64::NEG_INFINITY);
2449+
check_gt(1.0_f64, -f64::NAN);
2450+
check_lt(f64::INFINITY, f64::NAN);
2451+
check_gt(f64::NAN, 1.0_f64);
2452+
2453+
check_lt(-f32::NAN, f32::NEG_INFINITY);
2454+
check_gt(1.0_f32, -f32::NAN);
2455+
check_lt(f32::INFINITY, f32::NAN);
2456+
check_gt(f32::NAN, 1.0_f32);
2457+
}
23442458
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub use crate::bounds::Bounded;
3131
#[cfg(any(feature = "std", feature = "libm"))]
3232
pub use crate::float::Float;
3333
pub use crate::float::FloatConst;
34+
pub use crate::float::TotalOrder;
3435
// pub use real::{FloatCore, Real}; // NOTE: Don't do this, it breaks `use num_traits::*;`.
3536
pub use crate::cast::{cast, AsPrimitive, FromPrimitive, NumCast, ToPrimitive};
3637
pub use crate::identities::{one, zero, One, Zero};

0 commit comments

Comments
 (0)