Skip to content

Commit 68bab3e

Browse files
committed
Add total_cmp to f32 and f64, plus tests
1 parent 215f2d3 commit 68bab3e

File tree

5 files changed

+433
-0
lines changed

5 files changed

+433
-0
lines changed

src/libcore/num/f32.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,4 +810,77 @@ impl f32 {
810810
pub fn from_ne_bytes(bytes: [u8; 4]) -> Self {
811811
Self::from_bits(u32::from_ne_bytes(bytes))
812812
}
813+
814+
/// Returns an ordering between self and other values.
815+
/// Unlike the standard partial comparison between floating point numbers,
816+
/// this comparison always produces an ordering in accordance to
817+
/// the totalOrder predicate as defined in IEEE 754 (2008 revision)
818+
/// floating point standard. The values are ordered in following order:
819+
/// - Negative quiet NaN
820+
/// - Negative signaling NaN
821+
/// - Negative infinity
822+
/// - Negative numbers
823+
/// - Negative subnormal numbers
824+
/// - Negative zero
825+
/// - Positive zero
826+
/// - Positive subnormal numbers
827+
/// - Positive numbers
828+
/// - Positive infinity
829+
/// - Positive signaling NaN
830+
/// - Positive quiet NaN
831+
///
832+
/// # Example
833+
/// ```
834+
/// #![feature(total_cmp)]
835+
/// struct GoodBoy {
836+
/// name: String,
837+
/// weight: f32,
838+
/// }
839+
///
840+
/// let mut bois = vec![
841+
/// GoodBoy { name: "Pucci".to_owned(), weight: 0.1 },
842+
/// GoodBoy { name: "Woofer".to_owned(), weight: 99.0 },
843+
/// GoodBoy { name: "Yapper".to_owned(), weight: 10.0 },
844+
/// GoodBoy { name: "Chonk".to_owned(), weight: f32::INFINITY },
845+
/// GoodBoy { name: "Abs. Unit".to_owned(), weight: f32::NAN },
846+
/// GoodBoy { name: "Floaty".to_owned(), weight: -5.0 },
847+
/// ];
848+
///
849+
/// bois.sort_by(|a, b| a.weight.total_cmp(&b.weight));
850+
/// # assert!(bois.into_iter().map(|b| b.weight)
851+
/// # .zip([-5.0, 0.1, 10.0, 99.0, f32::INFINITY, f32::NAN].iter())
852+
/// # .all(|(a, b)| a.to_bits() == b.to_bits()))
853+
/// ```
854+
#[must_use = "method returns a new number and does not mutate the original value"]
855+
#[unstable(feature = "total_cmp", issue = "none")]
856+
#[inline]
857+
pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering {
858+
let mut left = self.to_bits() as i32;
859+
let mut right = other.to_bits() as i32;
860+
861+
// In case of negatives, flip all the bits expect the sign
862+
// to achieve a similar layout as two's complement integers
863+
//
864+
// Why does this work? IEEE 754 floats consist of three fields:
865+
// Sign bit, exponent and mantissa. The set of exponent and mantissa
866+
// fields as a whole have the property that their bitwise order is
867+
// equal to the numeric magnitude where the magnitude is defined.
868+
// The magnitude is not normally defined on NaN values, but
869+
// IEEE 754 totalOrder defines the NaN values also to follow the
870+
// bitwise order. This leads to order explained in the doc comment.
871+
// However, the representation of magnitude is the same for negative
872+
// and positive numbers – only the sign bit is different.
873+
// To easily compare the floats as signed integers, we need to
874+
// flip the exponent and mantissa bits in case of negative numbers.
875+
// We effectively convert the numbers to "two's complement" form.
876+
if left < 0 {
877+
// i32::MAX corresponds the bit pattern of "all ones expect for the sign bit"
878+
left ^= i32::MAX
879+
};
880+
if right < 0 {
881+
right ^= i32::MAX
882+
};
883+
884+
left.cmp(&right)
885+
}
813886
}

src/libcore/num/f64.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,4 +824,77 @@ impl f64 {
824824
pub fn from_ne_bytes(bytes: [u8; 8]) -> Self {
825825
Self::from_bits(u64::from_ne_bytes(bytes))
826826
}
827+
828+
/// Returns an ordering between self and other values.
829+
/// Unlike the standard partial comparison between floating point numbers,
830+
/// this comparison always produces an ordering in accordance to
831+
/// the totalOrder predicate as defined in IEEE 754 (2008 revision)
832+
/// floating point standard. The values are ordered in following order:
833+
/// - Negative quiet NaN
834+
/// - Negative signaling NaN
835+
/// - Negative infinity
836+
/// - Negative numbers
837+
/// - Negative subnormal numbers
838+
/// - Negative zero
839+
/// - Positive zero
840+
/// - Positive subnormal numbers
841+
/// - Positive numbers
842+
/// - Positive infinity
843+
/// - Positive signaling NaN
844+
/// - Positive quiet NaN
845+
///
846+
/// # Example
847+
/// ```
848+
/// #![feature(total_cmp)]
849+
/// struct GoodBoy {
850+
/// name: String,
851+
/// weight: f64,
852+
/// }
853+
///
854+
/// let mut bois = vec![
855+
/// GoodBoy { name: "Pucci".to_owned(), weight: 0.1 },
856+
/// GoodBoy { name: "Woofer".to_owned(), weight: 99.0 },
857+
/// GoodBoy { name: "Yapper".to_owned(), weight: 10.0 },
858+
/// GoodBoy { name: "Chonk".to_owned(), weight: f64::INFINITY },
859+
/// GoodBoy { name: "Abs. Unit".to_owned(), weight: f64::NAN },
860+
/// GoodBoy { name: "Floaty".to_owned(), weight: -5.0 },
861+
/// ];
862+
///
863+
/// bois.sort_by(|a, b| a.weight.total_cmp(&b.weight));
864+
/// # assert!(bois.into_iter().map(|b| b.weight)
865+
/// # .zip([-5.0, 0.1, 10.0, 99.0, f64::INFINITY, f64::NAN].iter())
866+
/// # .all(|(a, b)| a.to_bits() == b.to_bits()))
867+
/// ```
868+
#[must_use = "method returns a new number and does not mutate the original value"]
869+
#[unstable(feature = "total_cmp", issue = "none")]
870+
#[inline]
871+
pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering {
872+
let mut left = self.to_bits() as i64;
873+
let mut right = other.to_bits() as i64;
874+
875+
// In case of negatives, flip all the bits expect the sign
876+
// to achieve a similar layout as two's complement integers
877+
//
878+
// Why does this work? IEEE 754 floats consist of three fields:
879+
// Sign bit, exponent and mantissa. The set of exponent and mantissa
880+
// fields as a whole have the property that their bitwise order is
881+
// equal to the numeric magnitude where the magnitude is defined.
882+
// The magnitude is not normally defined on NaN values, but
883+
// IEEE 754 totalOrder defines the NaN values also to follow the
884+
// bitwise order. This leads to order explained in the doc comment.
885+
// However, the representation of magnitude is the same for negative
886+
// and positive numbers – only the sign bit is different.
887+
// To easily compare the floats as signed integers, we need to
888+
// flip the exponent and mantissa bits in case of negative numbers.
889+
// We effectively convert the numbers to "two's complement" form.
890+
if left < 0 {
891+
// i64::MAX corresponds the bit pattern of "all ones expect for the sign bit"
892+
left ^= i64::MAX
893+
};
894+
if right < 0 {
895+
right ^= i64::MAX
896+
};
897+
898+
left.cmp(&right)
899+
}
827900
}

src/libstd/f32.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,4 +1531,147 @@ mod tests {
15311531
fn test_clamp_max_is_nan() {
15321532
let _ = 1.0f32.clamp(3.0, NAN);
15331533
}
1534+
1535+
#[test]
1536+
fn test_total_cmp() {
1537+
use core::cmp::Ordering;
1538+
1539+
fn quiet_bit_mask() -> u32 {
1540+
1 << (f32::MANTISSA_DIGITS - 2)
1541+
}
1542+
1543+
fn min_subnorm() -> f32 {
1544+
f32::MIN_POSITIVE / f32::powf(2.0, f32::MANTISSA_DIGITS as f32 - 1.0)
1545+
}
1546+
1547+
fn max_subnorm() -> f32 {
1548+
f32::MIN_POSITIVE - min_subnorm()
1549+
}
1550+
1551+
fn q_nan() -> f32 {
1552+
f32::from_bits(f32::NAN.to_bits() | quiet_bit_mask())
1553+
}
1554+
1555+
fn s_nan() -> f32 {
1556+
f32::from_bits((f32::NAN.to_bits() & !quiet_bit_mask()) + 42)
1557+
}
1558+
1559+
assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan()));
1560+
assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan()));
1561+
assert_eq!(Ordering::Equal, (-f32::INFINITY).total_cmp(&-f32::INFINITY));
1562+
assert_eq!(Ordering::Equal, (-f32::MAX).total_cmp(&-f32::MAX));
1563+
assert_eq!(Ordering::Equal, (-2.5_f32).total_cmp(&-2.5));
1564+
assert_eq!(Ordering::Equal, (-1.0_f32).total_cmp(&-1.0));
1565+
assert_eq!(Ordering::Equal, (-1.5_f32).total_cmp(&-1.5));
1566+
assert_eq!(Ordering::Equal, (-0.5_f32).total_cmp(&-0.5));
1567+
assert_eq!(Ordering::Equal, (-f32::MIN_POSITIVE).total_cmp(&-f32::MIN_POSITIVE));
1568+
assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm()));
1569+
assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm()));
1570+
assert_eq!(Ordering::Equal, (-0.0_f32).total_cmp(&-0.0));
1571+
assert_eq!(Ordering::Equal, 0.0_f32.total_cmp(&0.0));
1572+
assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm()));
1573+
assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm()));
1574+
assert_eq!(Ordering::Equal, f32::MIN_POSITIVE.total_cmp(&f32::MIN_POSITIVE));
1575+
assert_eq!(Ordering::Equal, 0.5_f32.total_cmp(&0.5));
1576+
assert_eq!(Ordering::Equal, 1.0_f32.total_cmp(&1.0));
1577+
assert_eq!(Ordering::Equal, 1.5_f32.total_cmp(&1.5));
1578+
assert_eq!(Ordering::Equal, 2.5_f32.total_cmp(&2.5));
1579+
assert_eq!(Ordering::Equal, f32::MAX.total_cmp(&f32::MAX));
1580+
assert_eq!(Ordering::Equal, f32::INFINITY.total_cmp(&f32::INFINITY));
1581+
assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan()));
1582+
assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan()));
1583+
1584+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan()));
1585+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::INFINITY));
1586+
assert_eq!(Ordering::Less, (-f32::INFINITY).total_cmp(&-f32::MAX));
1587+
assert_eq!(Ordering::Less, (-f32::MAX).total_cmp(&-2.5));
1588+
assert_eq!(Ordering::Less, (-2.5_f32).total_cmp(&-1.5));
1589+
assert_eq!(Ordering::Less, (-1.5_f32).total_cmp(&-1.0));
1590+
assert_eq!(Ordering::Less, (-1.0_f32).total_cmp(&-0.5));
1591+
assert_eq!(Ordering::Less, (-0.5_f32).total_cmp(&-f32::MIN_POSITIVE));
1592+
assert_eq!(Ordering::Less, (-f32::MIN_POSITIVE).total_cmp(&-max_subnorm()));
1593+
assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm()));
1594+
assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0));
1595+
assert_eq!(Ordering::Less, (-0.0_f32).total_cmp(&0.0));
1596+
assert_eq!(Ordering::Less, 0.0_f32.total_cmp(&min_subnorm()));
1597+
assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm()));
1598+
assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f32::MIN_POSITIVE));
1599+
assert_eq!(Ordering::Less, f32::MIN_POSITIVE.total_cmp(&0.5));
1600+
assert_eq!(Ordering::Less, 0.5_f32.total_cmp(&1.0));
1601+
assert_eq!(Ordering::Less, 1.0_f32.total_cmp(&1.5));
1602+
assert_eq!(Ordering::Less, 1.5_f32.total_cmp(&2.5));
1603+
assert_eq!(Ordering::Less, 2.5_f32.total_cmp(&f32::MAX));
1604+
assert_eq!(Ordering::Less, f32::MAX.total_cmp(&f32::INFINITY));
1605+
assert_eq!(Ordering::Less, f32::INFINITY.total_cmp(&s_nan()));
1606+
assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan()));
1607+
1608+
assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan()));
1609+
assert_eq!(Ordering::Greater, (-f32::INFINITY).total_cmp(&-s_nan()));
1610+
assert_eq!(Ordering::Greater, (-f32::MAX).total_cmp(&-f32::INFINITY));
1611+
assert_eq!(Ordering::Greater, (-2.5_f32).total_cmp(&-f32::MAX));
1612+
assert_eq!(Ordering::Greater, (-1.5_f32).total_cmp(&-2.5));
1613+
assert_eq!(Ordering::Greater, (-1.0_f32).total_cmp(&-1.5));
1614+
assert_eq!(Ordering::Greater, (-0.5_f32).total_cmp(&-1.0));
1615+
assert_eq!(Ordering::Greater, (-f32::MIN_POSITIVE).total_cmp(&-0.5));
1616+
assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f32::MIN_POSITIVE));
1617+
assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm()));
1618+
assert_eq!(Ordering::Greater, (-0.0_f32).total_cmp(&-min_subnorm()));
1619+
assert_eq!(Ordering::Greater, 0.0_f32.total_cmp(&-0.0));
1620+
assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0));
1621+
assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm()));
1622+
assert_eq!(Ordering::Greater, f32::MIN_POSITIVE.total_cmp(&max_subnorm()));
1623+
assert_eq!(Ordering::Greater, 0.5_f32.total_cmp(&f32::MIN_POSITIVE));
1624+
assert_eq!(Ordering::Greater, 1.0_f32.total_cmp(&0.5));
1625+
assert_eq!(Ordering::Greater, 1.5_f32.total_cmp(&1.0));
1626+
assert_eq!(Ordering::Greater, 2.5_f32.total_cmp(&1.5));
1627+
assert_eq!(Ordering::Greater, f32::MAX.total_cmp(&2.5));
1628+
assert_eq!(Ordering::Greater, f32::INFINITY.total_cmp(&f32::MAX));
1629+
assert_eq!(Ordering::Greater, s_nan().total_cmp(&f32::INFINITY));
1630+
assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan()));
1631+
1632+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan()));
1633+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f32::INFINITY));
1634+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f32::MAX));
1635+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5));
1636+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5));
1637+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0));
1638+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5));
1639+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f32::MIN_POSITIVE));
1640+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm()));
1641+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm()));
1642+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0));
1643+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0));
1644+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm()));
1645+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm()));
1646+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f32::MIN_POSITIVE));
1647+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5));
1648+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0));
1649+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5));
1650+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5));
1651+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f32::MAX));
1652+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f32::INFINITY));
1653+
assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan()));
1654+
1655+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::INFINITY));
1656+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::MAX));
1657+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5));
1658+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5));
1659+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0));
1660+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5));
1661+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::MIN_POSITIVE));
1662+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm()));
1663+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm()));
1664+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0));
1665+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0));
1666+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm()));
1667+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm()));
1668+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::MIN_POSITIVE));
1669+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5));
1670+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0));
1671+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5));
1672+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5));
1673+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::MAX));
1674+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::INFINITY));
1675+
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
1676+
}
15341677
}

0 commit comments

Comments
 (0)