Skip to content

Commit 61d9a1b

Browse files
bors[bot]andrewjradcliffecuviper
authored
Merge #295
295: `TotalOrder` trait for floating point numbers r=cuviper a=andrewjradcliffe 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). If the crate designers could weigh in on whether to introduce constraints, that would be helpful. Resolves: [issue#256](#256) Co-authored-by: Andrew Radcliffe <[email protected]> Co-authored-by: Josh Stone <[email protected]>
2 parents f832428 + aeee038 commit 61d9a1b

File tree

5 files changed

+142
-1
lines changed

5 files changed

+142
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
1.44.0, # has_to_int_unchecked
2121
1.46.0, # has_leading_trailing_ones
2222
1.53.0, # has_is_subnormal
23+
1.62.0, # has_total_cmp
2324
stable,
2425
beta,
2526
nightly,

bors.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ status = [
66
"Test (1.44.0)",
77
"Test (1.46.0)",
88
"Test (1.53.0)",
9+
"Test (1.62.0)",
910
"Test (stable)",
1011
"Test (beta)",
1112
"Test (nightly)",

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ fn main() {
1616
ac.emit_expression_cfg("1f64.copysign(-1f64)", "has_copysign");
1717
}
1818
ac.emit_expression_cfg("1f64.is_subnormal()", "has_is_subnormal");
19+
ac.emit_expression_cfg("1f64.total_cmp(&2f64)", "has_total_cmp");
1920

2021
ac.emit_expression_cfg("1u32.to_ne_bytes()", "has_int_to_from_bytes");
2122
ac.emit_expression_cfg("3.14f64.to_ne_bytes()", "has_float_to_from_bytes");

ci/rustup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
set -ex
66

77
ci=$(dirname $0)
8-
for version in 1.31.0 1.35.0 1.37.0 1.38.0 1.44.0 1.46.0 1.53.0 stable beta nightly; do
8+
for version in 1.31.0 1.35.0 1.37.0 1.38.0 1.44.0 1.46.0 1.53.0 1.62.0 stable beta nightly; do
99
rustup run "$version" "$ci/test_full.sh"
1010
done

src/float.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use core::cmp::Ordering;
12
use core::num::FpCategory;
23
use core::ops::{Add, Div, Neg};
34

@@ -2210,6 +2211,89 @@ float_const_impl! {
22102211
SQRT_2,
22112212
}
22122213

2214+
/// Trait for floating point numbers that provide an implementation
2215+
/// of the `totalOrder` predicate as defined in the IEEE 754 (2008 revision)
2216+
/// floating point standard.
2217+
pub trait TotalOrder {
2218+
/// Return the ordering between `self` and `other`.
2219+
///
2220+
/// Unlike the standard partial comparison between floating point numbers,
2221+
/// this comparison always produces an ordering in accordance to
2222+
/// the `totalOrder` predicate as defined in the IEEE 754 (2008 revision)
2223+
/// floating point standard. The values are ordered in the following sequence:
2224+
///
2225+
/// - negative quiet NaN
2226+
/// - negative signaling NaN
2227+
/// - negative infinity
2228+
/// - negative numbers
2229+
/// - negative subnormal numbers
2230+
/// - negative zero
2231+
/// - positive zero
2232+
/// - positive subnormal numbers
2233+
/// - positive numbers
2234+
/// - positive infinity
2235+
/// - positive signaling NaN
2236+
/// - positive quiet NaN.
2237+
///
2238+
/// The ordering established by this function does not always agree with the
2239+
/// [`PartialOrd`] and [`PartialEq`] implementations. For example,
2240+
/// they consider negative and positive zero equal, while `total_cmp`
2241+
/// doesn't.
2242+
///
2243+
/// The interpretation of the signaling NaN bit follows the definition in
2244+
/// the IEEE 754 standard, which may not match the interpretation by some of
2245+
/// the older, non-conformant (e.g. MIPS) hardware implementations.
2246+
///
2247+
/// # Examples
2248+
/// ```
2249+
/// use num_traits::float::TotalOrder;
2250+
/// use std::cmp::Ordering;
2251+
/// use std::{f32, f64};
2252+
///
2253+
/// fn check_eq<T: TotalOrder>(x: T, y: T) {
2254+
/// assert_eq!(x.total_cmp(&y), Ordering::Equal);
2255+
/// }
2256+
///
2257+
/// check_eq(f64::NAN, f64::NAN);
2258+
/// check_eq(f32::NAN, f32::NAN);
2259+
///
2260+
/// fn check_lt<T: TotalOrder>(x: T, y: T) {
2261+
/// assert_eq!(x.total_cmp(&y), Ordering::Less);
2262+
/// }
2263+
///
2264+
/// check_lt(-f64::NAN, f64::NAN);
2265+
/// check_lt(f64::INFINITY, f64::NAN);
2266+
/// check_lt(-0.0_f64, 0.0_f64);
2267+
/// ```
2268+
fn total_cmp(&self, other: &Self) -> Ordering;
2269+
}
2270+
macro_rules! totalorder_impl {
2271+
($T:ident, $I:ident, $U:ident, $bits:expr) => {
2272+
impl TotalOrder for $T {
2273+
#[inline]
2274+
#[cfg(has_total_cmp)]
2275+
fn total_cmp(&self, other: &Self) -> Ordering {
2276+
// Forward to the core implementation
2277+
Self::total_cmp(&self, other)
2278+
}
2279+
#[inline]
2280+
#[cfg(not(has_total_cmp))]
2281+
fn total_cmp(&self, other: &Self) -> Ordering {
2282+
// Backport the core implementation (since 1.62)
2283+
let mut left = self.to_bits() as $I;
2284+
let mut right = other.to_bits() as $I;
2285+
2286+
left ^= (((left >> ($bits - 1)) as $U) >> 1) as $I;
2287+
right ^= (((right >> ($bits - 1)) as $U) >> 1) as $I;
2288+
2289+
left.cmp(&right)
2290+
}
2291+
}
2292+
};
2293+
}
2294+
totalorder_impl!(f64, i64, u64, 64);
2295+
totalorder_impl!(f32, i32, u32, 32);
2296+
22132297
#[cfg(test)]
22142298
mod tests {
22152299
use core::f64::consts;
@@ -2341,4 +2425,58 @@ mod tests {
23412425
test_subnormal::<f64>();
23422426
test_subnormal::<f32>();
23432427
}
2428+
2429+
#[test]
2430+
fn total_cmp() {
2431+
use crate::float::TotalOrder;
2432+
use core::cmp::Ordering;
2433+
use core::{f32, f64};
2434+
2435+
fn check_eq<T: TotalOrder>(x: T, y: T) {
2436+
assert_eq!(x.total_cmp(&y), Ordering::Equal);
2437+
}
2438+
fn check_lt<T: TotalOrder>(x: T, y: T) {
2439+
assert_eq!(x.total_cmp(&y), Ordering::Less);
2440+
}
2441+
fn check_gt<T: TotalOrder>(x: T, y: T) {
2442+
assert_eq!(x.total_cmp(&y), Ordering::Greater);
2443+
}
2444+
2445+
check_eq(f64::NAN, f64::NAN);
2446+
check_eq(f32::NAN, f32::NAN);
2447+
2448+
check_lt(-0.0_f64, 0.0_f64);
2449+
check_lt(-0.0_f32, 0.0_f32);
2450+
2451+
// x87 registers don't preserve the exact value of signaling NaN:
2452+
// https://github.com/rust-lang/rust/issues/115567
2453+
#[cfg(not(target_arch = "x86"))]
2454+
{
2455+
let s_nan = f64::from_bits(0x7ff4000000000000);
2456+
let q_nan = f64::from_bits(0x7ff8000000000000);
2457+
check_lt(s_nan, q_nan);
2458+
2459+
let neg_s_nan = f64::from_bits(0xfff4000000000000);
2460+
let neg_q_nan = f64::from_bits(0xfff8000000000000);
2461+
check_lt(neg_q_nan, neg_s_nan);
2462+
2463+
let s_nan = f32::from_bits(0x7fa00000);
2464+
let q_nan = f32::from_bits(0x7fc00000);
2465+
check_lt(s_nan, q_nan);
2466+
2467+
let neg_s_nan = f32::from_bits(0xffa00000);
2468+
let neg_q_nan = f32::from_bits(0xffc00000);
2469+
check_lt(neg_q_nan, neg_s_nan);
2470+
}
2471+
2472+
check_lt(-f64::NAN, f64::NEG_INFINITY);
2473+
check_gt(1.0_f64, -f64::NAN);
2474+
check_lt(f64::INFINITY, f64::NAN);
2475+
check_gt(f64::NAN, 1.0_f64);
2476+
2477+
check_lt(-f32::NAN, f32::NEG_INFINITY);
2478+
check_gt(1.0_f32, -f32::NAN);
2479+
check_lt(f32::INFINITY, f32::NAN);
2480+
check_gt(f32::NAN, 1.0_f32);
2481+
}
23442482
}

0 commit comments

Comments
 (0)