about summary refs log tree commit diff
path: root/src/libcore
diff options
context:
space:
mode:
authorPyry Kontio <pyry.kontio@drasa.eu>2020-05-23 04:41:24 +0900
committerPyry Kontio <pyry.kontio@drasa.eu>2020-05-25 20:30:55 +0900
commit68bab3e56e7df618c093f5b93901f5319ef65997 (patch)
treeb1ca87802092315af9b9f22c527fadae34142793 /src/libcore
parent215f2d3294b08dbdcf8f7d40de21ef1e7eae0a2d (diff)
Add total_cmp to f32 and f64, plus tests
Diffstat (limited to 'src/libcore')
-rw-r--r--src/libcore/num/f32.rs73
-rw-r--r--src/libcore/num/f64.rs73
2 files changed, 146 insertions, 0 deletions
diff --git a/src/libcore/num/f32.rs b/src/libcore/num/f32.rs
index 434569020d2..47f489c40ab 100644
--- a/src/libcore/num/f32.rs
+++ b/src/libcore/num/f32.rs
@@ -810,4 +810,77 @@ impl f32 {
     pub fn from_ne_bytes(bytes: [u8; 4]) -> Self {
         Self::from_bits(u32::from_ne_bytes(bytes))
     }
+
+    /// Returns an ordering between self and other values.
+    /// Unlike the standard partial comparison between floating point numbers,
+    /// this comparison always produces an ordering in accordance to
+    /// the totalOrder predicate as defined in IEEE 754 (2008 revision)
+    /// floating point standard. The values are ordered in following order:
+    /// - 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
+    ///
+    /// # Example
+    /// ```
+    /// #![feature(total_cmp)]
+    /// struct GoodBoy {
+    ///     name: String,
+    ///     weight: f32,
+    /// }
+    ///
+    /// let mut bois = vec![
+    ///     GoodBoy { name: "Pucci".to_owned(), weight: 0.1 },
+    ///     GoodBoy { name: "Woofer".to_owned(), weight: 99.0 },
+    ///     GoodBoy { name: "Yapper".to_owned(), weight: 10.0 },
+    ///     GoodBoy { name: "Chonk".to_owned(), weight: f32::INFINITY },
+    ///     GoodBoy { name: "Abs. Unit".to_owned(), weight: f32::NAN },
+    ///     GoodBoy { name: "Floaty".to_owned(), weight: -5.0 },
+    /// ];
+    ///
+    /// bois.sort_by(|a, b| a.weight.total_cmp(&b.weight));
+    /// # assert!(bois.into_iter().map(|b| b.weight)
+    /// #     .zip([-5.0, 0.1, 10.0, 99.0, f32::INFINITY, f32::NAN].iter())
+    /// #     .all(|(a, b)| a.to_bits() == b.to_bits()))
+    /// ```
+    #[must_use = "method returns a new number and does not mutate the original value"]
+    #[unstable(feature = "total_cmp", issue = "none")]
+    #[inline]
+    pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering {
+        let mut left = self.to_bits() as i32;
+        let mut right = other.to_bits() as i32;
+
+        // In case of negatives, flip all the bits expect the sign
+        // to achieve a similar layout as two's complement integers
+        //
+        // Why does this work? IEEE 754 floats consist of three fields:
+        // Sign bit, exponent and mantissa. The set of exponent and mantissa
+        // fields as a whole have the property that their bitwise order is
+        // equal to the numeric magnitude where the magnitude is defined.
+        // The magnitude is not normally defined on NaN values, but
+        // IEEE 754 totalOrder defines the NaN values also to follow the
+        // bitwise order. This leads to order explained in the doc comment.
+        // However, the representation of magnitude is the same for negative
+        // and positive numbers – only the sign bit is different.
+        // To easily compare the floats as signed integers, we need to
+        // flip the exponent and mantissa bits in case of negative numbers.
+        // We effectively convert the numbers to "two's complement" form.
+        if left < 0 {
+            // i32::MAX corresponds the bit pattern of "all ones expect for the sign bit"
+            left ^= i32::MAX
+        };
+        if right < 0 {
+            right ^= i32::MAX
+        };
+
+        left.cmp(&right)
+    }
 }
diff --git a/src/libcore/num/f64.rs b/src/libcore/num/f64.rs
index 6476ddb4541..9c021808380 100644
--- a/src/libcore/num/f64.rs
+++ b/src/libcore/num/f64.rs
@@ -824,4 +824,77 @@ impl f64 {
     pub fn from_ne_bytes(bytes: [u8; 8]) -> Self {
         Self::from_bits(u64::from_ne_bytes(bytes))
     }
+
+    /// Returns an ordering between self and other values.
+    /// Unlike the standard partial comparison between floating point numbers,
+    /// this comparison always produces an ordering in accordance to
+    /// the totalOrder predicate as defined in IEEE 754 (2008 revision)
+    /// floating point standard. The values are ordered in following order:
+    /// - 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
+    ///
+    /// # Example
+    /// ```
+    /// #![feature(total_cmp)]
+    /// struct GoodBoy {
+    ///     name: String,
+    ///     weight: f64,
+    /// }
+    ///
+    /// let mut bois = vec![
+    ///     GoodBoy { name: "Pucci".to_owned(), weight: 0.1 },
+    ///     GoodBoy { name: "Woofer".to_owned(), weight: 99.0 },
+    ///     GoodBoy { name: "Yapper".to_owned(), weight: 10.0 },
+    ///     GoodBoy { name: "Chonk".to_owned(), weight: f64::INFINITY },
+    ///     GoodBoy { name: "Abs. Unit".to_owned(), weight: f64::NAN },
+    ///     GoodBoy { name: "Floaty".to_owned(), weight: -5.0 },
+    /// ];
+    ///
+    /// bois.sort_by(|a, b| a.weight.total_cmp(&b.weight));
+    /// # assert!(bois.into_iter().map(|b| b.weight)
+    /// #     .zip([-5.0, 0.1, 10.0, 99.0, f64::INFINITY, f64::NAN].iter())
+    /// #     .all(|(a, b)| a.to_bits() == b.to_bits()))
+    /// ```
+    #[must_use = "method returns a new number and does not mutate the original value"]
+    #[unstable(feature = "total_cmp", issue = "none")]
+    #[inline]
+    pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering {
+        let mut left = self.to_bits() as i64;
+        let mut right = other.to_bits() as i64;
+
+        // In case of negatives, flip all the bits expect the sign
+        // to achieve a similar layout as two's complement integers
+        //
+        // Why does this work? IEEE 754 floats consist of three fields:
+        // Sign bit, exponent and mantissa. The set of exponent and mantissa
+        // fields as a whole have the property that their bitwise order is
+        // equal to the numeric magnitude where the magnitude is defined.
+        // The magnitude is not normally defined on NaN values, but
+        // IEEE 754 totalOrder defines the NaN values also to follow the
+        // bitwise order. This leads to order explained in the doc comment.
+        // However, the representation of magnitude is the same for negative
+        // and positive numbers – only the sign bit is different.
+        // To easily compare the floats as signed integers, we need to
+        // flip the exponent and mantissa bits in case of negative numbers.
+        // We effectively convert the numbers to "two's complement" form.
+        if left < 0 {
+            // i64::MAX corresponds the bit pattern of "all ones expect for the sign bit"
+            left ^= i64::MAX
+        };
+        if right < 0 {
+            right ^= i64::MAX
+        };
+
+        left.cmp(&right)
+    }
 }