Skip to content

Commit

Permalink
Merge #295
Browse files Browse the repository at this point in the history
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 <andrewjradcliffe@gmail.com>
Co-authored-by: Josh Stone <cuviper@gmail.com>
  • Loading branch information
3 people committed Oct 27, 2023
2 parents f832428 + 56210ef commit 9a6e992
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
1.44.0, # has_to_int_unchecked
1.46.0, # has_leading_trailing_ones
1.53.0, # has_is_subnormal
1.62.0, # has_total_cmp
stable,
beta,
nightly,
Expand Down
1 change: 1 addition & 0 deletions bors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ status = [
"Test (1.44.0)",
"Test (1.46.0)",
"Test (1.53.0)",
"Test (1.62.0)",
"Test (stable)",
"Test (beta)",
"Test (nightly)",
Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn main() {
ac.emit_expression_cfg("1f64.copysign(-1f64)", "has_copysign");
}
ac.emit_expression_cfg("1f64.is_subnormal()", "has_is_subnormal");
ac.emit_expression_cfg("1f64.total_cmp(&2f64)", "has_total_cmp");

ac.emit_expression_cfg("1u32.to_ne_bytes()", "has_int_to_from_bytes");
ac.emit_expression_cfg("3.14f64.to_ne_bytes()", "has_float_to_from_bytes");
Expand Down
2 changes: 1 addition & 1 deletion ci/rustup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
set -ex

ci=$(dirname $0)
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
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
rustup run "$version" "$ci/test_full.sh"
done
133 changes: 133 additions & 0 deletions src/float.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core::cmp::Ordering;
use core::num::FpCategory;
use core::ops::{Add, Div, Neg};

Expand Down Expand Up @@ -2210,6 +2211,89 @@ 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<T: TotalOrder>(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<T: TotalOrder>(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);

#[cfg(test)]
mod tests {
use core::f64::consts;
Expand Down Expand Up @@ -2341,4 +2425,53 @@ mod tests {
test_subnormal::<f64>();
test_subnormal::<f32>();
}

#[test]
fn total_cmp() {
use crate::float::TotalOrder;
use core::cmp::Ordering;
use core::{f32, f64};

fn check_eq<T: TotalOrder>(x: T, y: T) {
assert_eq!(x.total_cmp(&y), Ordering::Equal);
}
fn check_lt<T: TotalOrder>(x: T, y: T) {
assert_eq!(x.total_cmp(&y), Ordering::Less);
}
fn check_gt<T: TotalOrder>(x: T, y: T) {
assert_eq!(x.total_cmp(&y), Ordering::Greater);
}

check_eq(f64::NAN, f64::NAN);
check_eq(f32::NAN, f32::NAN);

check_lt(-0.0_f64, 0.0_f64);
check_lt(-0.0_f32, 0.0_f32);

let s_nan = f64::from_bits(0x7ff4000000000000);
let q_nan = f64::from_bits(0x7ff8000000000000);
check_lt(s_nan, q_nan);

let neg_s_nan = f64::from_bits(0xfff4000000000000);
let neg_q_nan = f64::from_bits(0xfff8000000000000);
check_lt(neg_q_nan, neg_s_nan);

let s_nan = f32::from_bits(0x7fa00000);
let q_nan = f32::from_bits(0x7fc00000);
check_lt(s_nan, q_nan);

let neg_s_nan = f32::from_bits(0xffa00000);
let neg_q_nan = f32::from_bits(0xffc00000);
check_lt(neg_q_nan, neg_s_nan);

check_lt(-f64::NAN, f64::NEG_INFINITY);
check_gt(1.0_f64, -f64::NAN);
check_lt(f64::INFINITY, f64::NAN);
check_gt(f64::NAN, 1.0_f64);

check_lt(-f32::NAN, f32::NEG_INFINITY);
check_gt(1.0_f32, -f32::NAN);
check_lt(f32::INFINITY, f32::NAN);
check_gt(f32::NAN, 1.0_f32);
}
}

0 comments on commit 9a6e992

Please sign in to comment.