Skip to content

Commit f2b9b53

Browse files
committed
Add a total ordering newtype, with implementations for floating-point
Thanks to rkruppe for the ordering details for the doc comments!
1 parent f99911a commit f2b9b53

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed

src/libcore/cmp.rs

+34
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,40 @@ impl<T: Ord> Ord for Reverse<T> {
462462
}
463463
}
464464

465+
/// A wrapper newtype for providing a total order on a type that's normally partial.
466+
///
467+
/// This is most commonly used for floating-point:
468+
///
469+
/// ```rust
470+
/// #![feature(float_total_cmp)]
471+
///
472+
/// use std::cmp::Total;
473+
///
474+
/// assert!(-0.0 == 0.0);
475+
/// assert!(Total(-0.0) < Total(0.0));
476+
/// assert_eq!(std::f32::NAN.partial_cmp(&0.0), None);
477+
/// assert_eq!(Total(std::f32::NAN).partial_cmp(&Total(0.0)), Some(std::cmp::Ordering::Greater));
478+
///
479+
/// let mut a = [3.0, 1.0, 2.0];
480+
/// // a.sort(); // ERROR, because floats are !Ord
481+
/// a.sort_by_key(|x| std::cmp::Total(*x)); // But this works!
482+
/// assert_eq!(a, [1.0, 2.0, 3.0]);
483+
///
484+
/// // By using `Total`, the struct can derive `Eq` and `Ord`.
485+
/// #[derive(PartialEq, Eq, PartialOrd, Ord)]
486+
/// struct MyData {
487+
/// foo: Total<f32>,
488+
/// bar: Total<f64>,
489+
/// }
490+
/// ```
491+
///
492+
/// It can also be used to provide both partial and total order implementations
493+
/// for an enum, if orders between variant don't make sense conceptually but
494+
/// are still desired for use as a `BTreeMap` key or similar.
495+
#[derive(Debug, Copy, Clone)]
496+
#[unstable(feature = "float_total_cmp", issue = "55339")]
497+
pub struct Total<T>(#[unstable(feature = "float_total_cmp", issue = "55339")] pub T);
498+
465499
/// Trait for types that form a [total order](https://en.wikipedia.org/wiki/Total_order).
466500
///
467501
/// An order is a total order if it is (for all `a`, `b` and `c`):

src/libcore/num/f32.rs

+132
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1818
#![stable(feature = "rust1", since = "1.0.0")]
1919

20+
use cmp;
2021
use mem;
2122
use num::FpCategory;
2223

@@ -474,4 +475,135 @@ impl f32 {
474475
// It turns out the safety issues with sNaN were overblown! Hooray!
475476
unsafe { mem::transmute(v) }
476477
}
478+
479+
/// A 3-way version of the IEEE `totalOrder` predicate.
480+
///
481+
/// This is most commonly used with `cmp::Total`, not directly.
482+
///
483+
/// This is useful in situations where you run into the fact that `f32` is
484+
/// `PartialOrd`, but not `Ord`, like sorting. If you need a total order
485+
/// on arbitrary floating-point numbers you should use this -- or one of
486+
/// the related `total_*` methods -- they're implemented efficiently using
487+
/// just a few bitwise operations.
488+
///
489+
/// This function mostly agrees with `PartialOrd` and the usual comparison
490+
/// operators in how it orders floating point numbers, but it additionally
491+
/// distinguishes negative from positive zero (it considers -0 as less than
492+
/// +0, whereas `==` considers them equal) and it orders Not-a-Number values
493+
/// relative to the numbers and distinguishes NaNs with different bit patterns.
494+
///
495+
/// NaNs with positive sign are ordered greater than all other floating-point
496+
/// values including positive infinity, while NaNs with negative sign are
497+
/// ordered the least, below negative infinity. Two different NaNs with the
498+
/// same sign are ordered first by whether the are signaling (signaling is
499+
/// less than quiet if the sign is positive, reverse if negative) and second
500+
/// by their payload interpreted as integer (reversed order if the sign is negative).
501+
///
502+
/// This means all different (canonical) floating point bit patterns are
503+
/// placed in a linear order, given below in ascending order:
504+
///
505+
/// - Quiet Not-a-Number with negative sign (ordered by payload, descending)
506+
/// - Signaling Not-a-Number with negative sign (ordered by payload, descending)
507+
/// - Negative infinity
508+
/// - Negative finite non-zero numbers (ordered in the usual way)
509+
/// - Negative zero
510+
/// - Positive zero
511+
/// - Positive finite non-zero numbers (ordered in the usual way)
512+
/// - Positive infinity
513+
/// - Signaling Not-a-Number with positive sign (ordered by payload, ascending)
514+
/// - Quiet Not-a-Number with positive sign (ordered by payload, ascending)
515+
///
516+
/// # Examples
517+
///
518+
/// Most follow the normal ordering:
519+
/// ```
520+
/// #![feature(float_total_cmp)]
521+
/// use std::cmp::Ordering::*;
522+
///
523+
/// assert_eq!(f32::total_cmp(1.0, 2.0), Less);
524+
/// assert_eq!(f32::total_cmp(1.0, 1.0), Equal);
525+
/// assert_eq!(f32::total_cmp(2.0, 1.0), Greater);
526+
/// assert_eq!(f32::total_cmp(-1.0, 1.0), Less);
527+
/// assert_eq!(f32::total_cmp(1.0, -1.0), Greater);
528+
/// assert_eq!(f32::total_cmp(-1.0, -2.0), Greater);
529+
/// assert_eq!(f32::total_cmp(-1.0, -1.0), Equal);
530+
/// assert_eq!(f32::total_cmp(-2.0, -1.0), Less);
531+
/// assert_eq!(f32::total_cmp(-std::f32::MAX, -1.0e9), Less);
532+
/// assert_eq!(f32::total_cmp(std::f32::MAX, 1.0e9), Greater);
533+
/// ```
534+
///
535+
/// Zeros and NaNs:
536+
/// ```
537+
/// #![feature(float_total_cmp)]
538+
/// use std::cmp::Ordering::*;
539+
///
540+
/// assert_eq!(f32::partial_cmp(&0.0, &-0.0), Some(Equal));
541+
/// assert_eq!(f32::total_cmp(-0.0, 0.0), Less);
542+
/// assert_eq!(f32::total_cmp(0.0, -0.0), Greater);
543+
///
544+
/// assert_eq!(f32::partial_cmp(&std::f32::NAN, &std::f32::NAN), None);
545+
/// assert_eq!(f32::total_cmp(-std::f32::NAN, std::f32::NAN), Less);
546+
/// assert_eq!(f32::total_cmp(std::f32::NAN, std::f32::NAN), Equal);
547+
/// assert_eq!(f32::total_cmp(std::f32::NAN, -std::f32::NAN), Greater);
548+
/// assert_eq!(f32::total_cmp(std::f32::NAN, std::f32::INFINITY), Greater);
549+
/// assert_eq!(f32::total_cmp(-std::f32::NAN, -std::f32::INFINITY), Less);
550+
/// ```
551+
#[unstable(feature = "float_total_cmp", issue = "55339")]
552+
#[inline]
553+
pub fn total_cmp(self, other: f32) -> cmp::Ordering {
554+
self.total_cmp_key().cmp(&other.total_cmp_key())
555+
}
556+
557+
#[inline]
558+
fn total_cmp_key(self) -> i32 {
559+
let x = self.to_bits() as i32;
560+
// Flip the bottom 31 bits if the high bit is set
561+
x ^ ((x >> 31) as u32 >> 1) as i32
562+
}
563+
}
564+
565+
#[unstable(feature = "float_total_cmp", issue = "55339")]
566+
impl PartialEq for cmp::Total<f32> {
567+
fn eq(&self, other: &Self) -> bool {
568+
self.0.to_bits() == other.0.to_bits()
569+
}
570+
}
571+
572+
#[unstable(feature = "float_total_cmp", issue = "55339")]
573+
impl Eq for cmp::Total<f32> {}
574+
575+
#[unstable(feature = "float_total_cmp", issue = "55339")]
576+
impl PartialOrd for cmp::Total<f32> {
577+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
578+
Some(self.cmp(other))
579+
}
580+
}
581+
582+
/// See `f32::total_cmp` for details.
583+
///
584+
/// Using this to sort floats:
585+
/// ```
586+
/// #![feature(float_total_cmp)]
587+
///
588+
/// let mut a = [
589+
/// 1.0,
590+
/// -1.0,
591+
/// 0.0,
592+
/// -0.0,
593+
/// std::f32::NAN,
594+
/// -std::f32::NAN,
595+
/// std::f32::INFINITY,
596+
/// std::f32::NEG_INFINITY,
597+
/// ];
598+
/// a.sort_by_key(|x| std::cmp::Total(*x));
599+
/// assert_eq!(
600+
/// format!("{:?}", a),
601+
/// "[NaN, -inf, -1.0, -0.0, 0.0, 1.0, inf, NaN]"
602+
/// );
603+
/// ```
604+
#[unstable(feature = "float_total_cmp", issue = "55339")]
605+
impl Ord for cmp::Total<f32> {
606+
fn cmp(&self, other: &Self) -> cmp::Ordering {
607+
self.0.total_cmp(other.0)
608+
}
477609
}

src/libcore/num/f64.rs

+132
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1818
#![stable(feature = "rust1", since = "1.0.0")]
1919

20+
use cmp;
2021
use mem;
2122
use num::FpCategory;
2223

@@ -487,4 +488,135 @@ impl f64 {
487488
// It turns out the safety issues with sNaN were overblown! Hooray!
488489
unsafe { mem::transmute(v) }
489490
}
491+
492+
/// A 3-way version of the IEEE `totalOrder` predicate.
493+
///
494+
/// This is most commonly used with `cmp::Total`, not directly.
495+
///
496+
/// This is useful in situations where you run into the fact that `f64` is
497+
/// `PartialOrd`, but not `Ord`, like sorting. If you need a total order
498+
/// on arbitrary floating-point numbers you should use this -- or one of
499+
/// the related `total_*` methods -- they're implemented efficiently using
500+
/// just a few bitwise operations.
501+
///
502+
/// This function mostly agrees with `PartialOrd` and the usual comparison
503+
/// operators in how it orders floating point numbers, but it additionally
504+
/// distinguishes negative from positive zero (it considers -0 as less than
505+
/// +0, whereas `==` considers them equal) and it orders Not-a-Number values
506+
/// relative to the numbers and distinguishes NaNs with different bit patterns.
507+
///
508+
/// NaNs with positive sign are ordered greater than all other floating-point
509+
/// values including positive infinity, while NaNs with negative sign are
510+
/// ordered the least, below negative infinity. Two different NaNs with the
511+
/// same sign are ordered first by whether the are signaling (signaling is
512+
/// less than quiet if the sign is positive, reverse if negative) and second
513+
/// by their payload interpreted as integer (reversed order if the sign is negative).
514+
///
515+
/// This means all different (canonical) floating point bit patterns are
516+
/// placed in a linear order, given below in ascending order:
517+
///
518+
/// - Quiet Not-a-Number with negative sign (ordered by payload, descending)
519+
/// - Signaling Not-a-Number with negative sign (ordered by payload, descending)
520+
/// - Negative infinity
521+
/// - Negative finite non-zero numbers (ordered in the usual way)
522+
/// - Negative zero
523+
/// - Positive zero
524+
/// - Positive finite non-zero numbers (ordered in the usual way)
525+
/// - Positive infinity
526+
/// - Signaling Not-a-Number with positive sign (ordered by payload, ascending)
527+
/// - Quiet Not-a-Number with positive sign (ordered by payload, ascending)
528+
///
529+
/// # Examples
530+
///
531+
/// Most follow the normal ordering:
532+
/// ```
533+
/// #![feature(float_total_cmp)]
534+
/// use std::cmp::Ordering::*;
535+
///
536+
/// assert_eq!(f64::total_cmp(1.0, 2.0), Less);
537+
/// assert_eq!(f64::total_cmp(1.0, 1.0), Equal);
538+
/// assert_eq!(f64::total_cmp(2.0, 1.0), Greater);
539+
/// assert_eq!(f64::total_cmp(-1.0, 1.0), Less);
540+
/// assert_eq!(f64::total_cmp(1.0, -1.0), Greater);
541+
/// assert_eq!(f64::total_cmp(-1.0, -2.0), Greater);
542+
/// assert_eq!(f64::total_cmp(-1.0, -1.0), Equal);
543+
/// assert_eq!(f64::total_cmp(-2.0, -1.0), Less);
544+
/// assert_eq!(f64::total_cmp(-std::f64::MAX, -1.0e9), Less);
545+
/// assert_eq!(f64::total_cmp(std::f64::MAX, 1.0e9), Greater);
546+
/// ```
547+
///
548+
/// Zeros and NaNs:
549+
/// ```
550+
/// #![feature(float_total_cmp)]
551+
/// use std::cmp::Ordering::*;
552+
///
553+
/// assert_eq!(f64::partial_cmp(&0.0, &-0.0), Some(Equal));
554+
/// assert_eq!(f64::total_cmp(-0.0, 0.0), Less);
555+
/// assert_eq!(f64::total_cmp(0.0, -0.0), Greater);
556+
///
557+
/// assert_eq!(f64::partial_cmp(&std::f64::NAN, &std::f64::NAN), None);
558+
/// assert_eq!(f64::total_cmp(-std::f64::NAN, std::f64::NAN), Less);
559+
/// assert_eq!(f64::total_cmp(std::f64::NAN, std::f64::NAN), Equal);
560+
/// assert_eq!(f64::total_cmp(std::f64::NAN, -std::f64::NAN), Greater);
561+
/// assert_eq!(f64::total_cmp(std::f64::NAN, std::f64::INFINITY), Greater);
562+
/// assert_eq!(f64::total_cmp(-std::f64::NAN, -std::f64::INFINITY), Less);
563+
/// ```
564+
#[unstable(feature = "float_total_cmp", issue = "55339")]
565+
#[inline]
566+
pub fn total_cmp(self, other: f64) -> cmp::Ordering {
567+
self.total_cmp_key().cmp(&other.total_cmp_key())
568+
}
569+
570+
#[inline]
571+
fn total_cmp_key(self) -> i64 {
572+
let x = self.to_bits() as i64;
573+
// Flip the bottom 31 bits if the high bit is set
574+
x ^ ((x >> 31) as u64 >> 1) as i64
575+
}
576+
}
577+
578+
#[unstable(feature = "float_total_cmp", issue = "55339")]
579+
impl PartialEq for cmp::Total<f64> {
580+
fn eq(&self, other: &Self) -> bool {
581+
self.0.to_bits() == other.0.to_bits()
582+
}
583+
}
584+
585+
#[unstable(feature = "float_total_cmp", issue = "55339")]
586+
impl Eq for cmp::Total<f64> {}
587+
588+
#[unstable(feature = "float_total_cmp", issue = "55339")]
589+
impl PartialOrd for cmp::Total<f64> {
590+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
591+
Some(self.cmp(other))
592+
}
593+
}
594+
595+
/// See `f64::total_cmp` for details.
596+
///
597+
/// Using this to sort floats:
598+
/// ```
599+
/// #![feature(float_total_cmp)]
600+
///
601+
/// let mut a = [
602+
/// 1.0,
603+
/// -1.0,
604+
/// 0.0,
605+
/// -0.0,
606+
/// std::f64::NAN,
607+
/// -std::f64::NAN,
608+
/// std::f64::INFINITY,
609+
/// std::f64::NEG_INFINITY,
610+
/// ];
611+
/// a.sort_by_key(|x| std::cmp::Total(*x));
612+
/// assert_eq!(
613+
/// format!("{:?}", a),
614+
/// "[NaN, -inf, -1.0, -0.0, 0.0, 1.0, inf, NaN]"
615+
/// );
616+
/// ```
617+
#[unstable(feature = "float_total_cmp", issue = "55339")]
618+
impl Ord for cmp::Total<f64> {
619+
fn cmp(&self, other: &Self) -> cmp::Ordering {
620+
self.0.total_cmp(other.0)
621+
}
490622
}

0 commit comments

Comments
 (0)