diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 21775c0a6ab0..222c5a498be0 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -163,6 +163,7 @@ #![feature(const_slice_index)] #![feature(const_is_char_boundary)] #![feature(const_cstr_methods)] +#![feature(range_cmp_scalar)] // // Language features: #![feature(abi_unadjusted)] diff --git a/library/core/src/ops/range.rs b/library/core/src/ops/range.rs index 728202835818..09128f804c9b 100644 --- a/library/core/src/ops/range.rs +++ b/library/core/src/ops/range.rs @@ -145,6 +145,62 @@ impl> Range { pub fn is_empty(&self) -> bool { !(self.start < self.end) } + + /// Compares the range to a scalar. Returns `Some(Ordering::Equal)`, + /// if the range contains the scalar. If not, returns `Some(Ordering::Less)` + /// or `Some(Ordering::Greater)`, depending on whether the range is + /// below or above the scalar. Returns `None` if the values are + /// uncomparable (for example, NaN) or if the range is degenerate + /// (i.e. end < start). + /// + /// # Examples + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # use core::cmp::Ordering; + /// assert_eq!((1..10).cmp_scalar(-1234), Some(Ordering::Greater)); + /// assert_eq!((1..10).cmp_scalar(0), Some(Ordering::Greater)); + /// assert_eq!((1..10).cmp_scalar(1), Some(Ordering::Equal)); + /// assert_eq!((1..10).cmp_scalar(5), Some(Ordering::Equal)); + /// assert_eq!((1..10).cmp_scalar(9), Some(Ordering::Equal)); + /// assert_eq!((1..10).cmp_scalar(10), Some(Ordering::Less)); + /// assert_eq!((1..10).cmp_scalar(1234), Some(Ordering::Less)); + /// assert_eq!((20..10).cmp_scalar(15), None); + /// assert_eq!((f32::NAN..f32::NAN).cmp_scalar(10.0), None); + /// ``` + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # fn main() -> Result<(), usize> { + /// # struct File { + /// # seqnum: u32, + /// # range: core::ops::Range, + /// # } + /// let files = vec![ + /// File { seqnum: 0, range: 0..1000 }, + /// File { seqnum: 1, range: 1000..2200 }, + /// File { seqnum: 2, range: 2200..3900 }, + /// File { seqnum: 3, range: 3900..5000 }, + /// ]; + /// let target = 1600; + /// let index = files.binary_search_by(|f| f.range.cmp_scalar(target).unwrap())?; + /// assert_eq!(files[index].seqnum, 1); + /// # Ok(()) } + /// ``` + #[unstable(feature = "range_cmp_scalar", issue = "none")] + pub fn cmp_scalar(&self, scalar: Idx) -> Option { + if self.end < self.start { + None + } else if self.end <= scalar { + Some(core::cmp::Ordering::Less) + } else if scalar < self.start { + Some(core::cmp::Ordering::Greater) + } else if self.start <= scalar && scalar < self.end { + Some(core::cmp::Ordering::Equal) + } else { + None + } + } } /// A range only bounded inclusively below (`start..`). @@ -221,6 +277,31 @@ impl> RangeFrom { { >::contains(self, item) } + + /// Compares the range to a scalar. Returns `Some(Ordering::Equal)`, + /// if the range contains the scalar. If not, the range + /// can be only above the scalar, so returns `Some(Ordering::Greater)`. + /// Returns `None` if the values are uncomparable (for example, NaN). + /// + /// # Examples + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # use core::cmp::Ordering; + /// assert_eq!((100..).cmp_scalar(50), Some(Ordering::Greater)); + /// assert_eq!((100..).cmp_scalar(100), Some(Ordering::Equal)); + /// assert_eq!((100..).cmp_scalar(150), Some(Ordering::Equal)); + /// ``` + #[unstable(feature = "range_cmp_scalar", issue = "none")] + pub fn cmp_scalar(&self, scalar: Idx) -> Option { + if scalar < self.start { + Some(core::cmp::Ordering::Greater) + } else if self.start <= scalar { + Some(core::cmp::Ordering::Equal) + } else { + None + } + } } /// A range only bounded exclusively above (`..end`). @@ -302,6 +383,31 @@ impl> RangeTo { { >::contains(self, item) } + + /// Compares the range to a scalar. Returns `Some(Ordering::Equal)`, + /// if the range contains the scalar. If not, the range + /// can be only below the scalar, so returns `Some(Ordering::Less)`. + /// Returns `None` if the values are uncomparable (for example, NaN). + /// + /// # Examples + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # use core::cmp::Ordering; + /// assert_eq!((..100).cmp_scalar(50), Some(Ordering::Equal)); + /// assert_eq!((..100).cmp_scalar(100), Some(Ordering::Less)); + /// assert_eq!((..100).cmp_scalar(150), Some(Ordering::Less)); + /// ``` + #[unstable(feature = "range_cmp_scalar", issue = "none")] + pub fn cmp_scalar(&self, scalar: Idx) -> Option { + if self.end <= scalar { + Some(core::cmp::Ordering::Less) + } else if scalar < self.end { + Some(core::cmp::Ordering::Equal) + } else { + None + } + } } /// A range bounded inclusively below and above (`start..=end`). @@ -539,6 +645,62 @@ impl> RangeInclusive { pub fn is_empty(&self) -> bool { self.exhausted || !(self.start <= self.end) } + + /// Compares the range to a scalar. Returns `Some(Ordering::Equal)`, + /// if the range contains the scalar. If not, returns `Some(Ordering::Less)` + /// or `Some(Ordering::Greater)`, depending on whether the range is + /// below or above the scalar. Returns `None` if the values are + /// uncomparable (for example, NaN) or if the range is degenerate + /// (i.e. end < start). + /// + /// # Examples + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # use core::cmp::Ordering; + /// assert_eq!((1..=10).cmp_scalar(-1234), Some(Ordering::Greater)); + /// assert_eq!((1..=10).cmp_scalar(0), Some(Ordering::Greater)); + /// assert_eq!((1..=10).cmp_scalar(1), Some(Ordering::Equal)); + /// assert_eq!((1..=10).cmp_scalar(5), Some(Ordering::Equal)); + /// assert_eq!((1..=10).cmp_scalar(9), Some(Ordering::Equal)); + /// assert_eq!((1..=10).cmp_scalar(10), Some(Ordering::Equal)); + /// assert_eq!((1..=10).cmp_scalar(1234), Some(Ordering::Less)); + /// assert_eq!((20..=10).cmp_scalar(15), None); + /// assert_eq!((f32::NAN..=f32::NAN).cmp_scalar(10.0), None); + /// ``` + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # fn main() -> Result<(), usize> { + /// # struct File { + /// # seqnum: u32, + /// # range: core::ops::RangeInclusive, + /// # } + /// let files = vec![ + /// File { seqnum: 0, range: 0..=999 }, + /// File { seqnum: 1, range: 1000..=2199 }, + /// File { seqnum: 2, range: 2200..=3899 }, + /// File { seqnum: 3, range: 3900..=4999 }, + /// ]; + /// let target = 1600; + /// let index = files.binary_search_by(|f| f.range.cmp_scalar(target).unwrap())?; + /// assert_eq!(files[index].seqnum, 1); + /// # Ok(()) } + /// ``` + #[unstable(feature = "range_cmp_scalar", issue = "none")] + pub fn cmp_scalar(&self, scalar: Idx) -> Option { + if *self.end() < *self.start() { + None + } else if *self.end() < scalar { + Some(core::cmp::Ordering::Less) + } else if scalar < *self.start() { + Some(core::cmp::Ordering::Greater) + } else if *self.start() <= scalar && scalar <= *self.end() { + Some(core::cmp::Ordering::Equal) + } else { + None + } + } } /// A range only bounded inclusively above (`..=end`). @@ -620,6 +782,31 @@ impl> RangeToInclusive { { >::contains(self, item) } + + /// Compares the range to a scalar. Returns `Some(Ordering::Equal)`, + /// if the range contains the scalar. If not, the range + /// can be only below the scalar, so returns `Some(Ordering::Less)`. + /// Returns `None` if the values are uncomparable (for example, NaN). + /// + /// # Examples + /// + /// ```rust + /// #![feature(range_cmp_scalar)] + /// # use core::cmp::Ordering; + /// assert_eq!((..=100).cmp_scalar(50), Some(Ordering::Equal)); + /// assert_eq!((..=100).cmp_scalar(100), Some(Ordering::Equal)); + /// assert_eq!((..=100).cmp_scalar(150), Some(Ordering::Less)); + /// ``` + #[unstable(feature = "range_cmp_scalar", issue = "none")] + pub fn cmp_scalar(&self, scalar: Idx) -> Option { + if self.end < scalar { + Some(core::cmp::Ordering::Less) + } else if scalar <= self.end { + Some(core::cmp::Ordering::Equal) + } else { + None + } + } } // RangeToInclusive cannot impl From> diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 46f603eaebac..a554c50b5124 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -101,6 +101,7 @@ #![feature(slice_flatten)] #![feature(provide_any)] #![feature(utf8_chunks)] +#![feature(range_cmp_scalar)] #![deny(unsafe_op_in_unsafe_fn)] extern crate test; diff --git a/library/core/tests/ops.rs b/library/core/tests/ops.rs index 0c81cba35b3d..1cdf8efb0fff 100644 --- a/library/core/tests/ops.rs +++ b/library/core/tests/ops.rs @@ -1,206 +1,8 @@ mod control_flow; +mod range; -use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; use core::ops::{Deref, DerefMut}; -// Test the Range structs and syntax. - -#[test] -fn test_range() { - let r = Range { start: 2, end: 10 }; - let mut count = 0; - for (i, ri) in r.enumerate() { - assert_eq!(ri, i + 2); - assert!(ri >= 2 && ri < 10); - count += 1; - } - assert_eq!(count, 8); -} - -#[test] -fn test_range_from() { - let r = RangeFrom { start: 2 }; - let mut count = 0; - for (i, ri) in r.take(10).enumerate() { - assert_eq!(ri, i + 2); - assert!(ri >= 2 && ri < 12); - count += 1; - } - assert_eq!(count, 10); -} - -#[test] -fn test_range_to() { - // Not much to test. - let _ = RangeTo { end: 42 }; -} - -#[test] -fn test_full_range() { - // Not much to test. - let _ = RangeFull; -} - -#[test] -fn test_range_inclusive() { - let mut r = RangeInclusive::new(1i8, 2); - assert_eq!(r.next(), Some(1)); - assert_eq!(r.next(), Some(2)); - assert_eq!(r.next(), None); - - r = RangeInclusive::new(127i8, 127); - assert_eq!(r.next(), Some(127)); - assert_eq!(r.next(), None); - - r = RangeInclusive::new(-128i8, -128); - assert_eq!(r.next_back(), Some(-128)); - assert_eq!(r.next_back(), None); - - // degenerate - r = RangeInclusive::new(1, -1); - assert_eq!(r.size_hint(), (0, Some(0))); - assert_eq!(r.next(), None); -} - -#[test] -fn test_range_to_inclusive() { - // Not much to test. - let _ = RangeToInclusive { end: 42 }; -} - -#[test] -fn test_range_is_empty() { - assert!(!(0.0..10.0).is_empty()); - assert!((-0.0..0.0).is_empty()); - assert!((10.0..0.0).is_empty()); - - assert!(!(f32::NEG_INFINITY..f32::INFINITY).is_empty()); - assert!((f32::EPSILON..f32::NAN).is_empty()); - assert!((f32::NAN..f32::EPSILON).is_empty()); - assert!((f32::NAN..f32::NAN).is_empty()); - - assert!(!(0.0..=10.0).is_empty()); - assert!(!(-0.0..=0.0).is_empty()); - assert!((10.0..=0.0).is_empty()); - - assert!(!(f32::NEG_INFINITY..=f32::INFINITY).is_empty()); - assert!((f32::EPSILON..=f32::NAN).is_empty()); - assert!((f32::NAN..=f32::EPSILON).is_empty()); - assert!((f32::NAN..=f32::NAN).is_empty()); -} - -#[test] -fn test_bound_cloned_unbounded() { - assert_eq!(Bound::<&u32>::Unbounded.cloned(), Bound::Unbounded); -} - -#[test] -fn test_bound_cloned_included() { - assert_eq!(Bound::Included(&3).cloned(), Bound::Included(3)); -} - -#[test] -fn test_bound_cloned_excluded() { - assert_eq!(Bound::Excluded(&3).cloned(), Bound::Excluded(3)); -} - -#[test] -#[allow(unused_comparisons)] -#[allow(unused_mut)] -fn test_range_syntax() { - let mut count = 0; - for i in 0_usize..10 { - assert!(i >= 0 && i < 10); - count += i; - } - assert_eq!(count, 45); - - let mut count = 0; - let mut range = 0_usize..10; - for i in range { - assert!(i >= 0 && i < 10); - count += i; - } - assert_eq!(count, 45); - - let mut count = 0; - let mut rf = 3_usize..; - for i in rf.take(10) { - assert!(i >= 3 && i < 13); - count += i; - } - assert_eq!(count, 75); - - let _ = 0_usize..4 + 4 - 3; - - fn foo() -> isize { - 42 - } - let _ = 0..foo(); - - let _ = { &42..&100 }; // references to literals are OK - let _ = ..42_usize; - - // Test we can use two different types with a common supertype. - let x = &42; - { - let y = 42; - let _ = x..&y; - } -} - -#[test] -#[allow(dead_code)] -fn test_range_syntax_in_return_statement() { - fn return_range_to() -> RangeTo { - return ..1; - } - fn return_full_range() -> RangeFull { - return ..; - } - // Not much to test. -} - -#[test] -fn range_structural_match() { - // test that all range types can be structurally matched upon - - const RANGE: Range = 0..1000; - match RANGE { - RANGE => {} - _ => unreachable!(), - } - - const RANGE_FROM: RangeFrom = 0..; - match RANGE_FROM { - RANGE_FROM => {} - _ => unreachable!(), - } - - const RANGE_FULL: RangeFull = ..; - match RANGE_FULL { - RANGE_FULL => {} - } - - const RANGE_INCLUSIVE: RangeInclusive = 0..=999; - match RANGE_INCLUSIVE { - RANGE_INCLUSIVE => {} - _ => unreachable!(), - } - - const RANGE_TO: RangeTo = ..1000; - match RANGE_TO { - RANGE_TO => {} - _ => unreachable!(), - } - - const RANGE_TO_INCLUSIVE: RangeToInclusive = ..=999; - match RANGE_TO_INCLUSIVE { - RANGE_TO_INCLUSIVE => {} - _ => unreachable!(), - } -} - // Test Deref implementations #[test] diff --git a/library/core/tests/ops/range.rs b/library/core/tests/ops/range.rs new file mode 100644 index 000000000000..a99587b04a67 --- /dev/null +++ b/library/core/tests/ops/range.rs @@ -0,0 +1,277 @@ +use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; + +// Test the Range structs and syntax. + +#[test] +fn test_range() { + let r = Range { start: 2, end: 10 }; + let mut count = 0; + for (i, ri) in r.enumerate() { + assert_eq!(ri, i + 2); + assert!(ri >= 2 && ri < 10); + count += 1; + } + assert_eq!(count, 8); +} + +#[test] +fn test_range_from() { + let r = RangeFrom { start: 2 }; + let mut count = 0; + for (i, ri) in r.take(10).enumerate() { + assert_eq!(ri, i + 2); + assert!(ri >= 2 && ri < 12); + count += 1; + } + assert_eq!(count, 10); +} + +#[test] +fn test_range_to() { + // Not much to test. + let _ = RangeTo { end: 42 }; +} + +#[test] +fn test_full_range() { + // Not much to test. + let _ = RangeFull; +} + +#[test] +fn test_range_inclusive() { + let mut r = RangeInclusive::new(1i8, 2); + assert_eq!(r.next(), Some(1)); + assert_eq!(r.next(), Some(2)); + assert_eq!(r.next(), None); + + r = RangeInclusive::new(127i8, 127); + assert_eq!(r.next(), Some(127)); + assert_eq!(r.next(), None); + + r = RangeInclusive::new(-128i8, -128); + assert_eq!(r.next_back(), Some(-128)); + assert_eq!(r.next_back(), None); + + // degenerate + r = RangeInclusive::new(1, -1); + assert_eq!(r.size_hint(), (0, Some(0))); + assert_eq!(r.next(), None); +} + +#[test] +fn test_range_to_inclusive() { + // Not much to test. + let _ = RangeToInclusive { end: 42 }; +} + +#[test] +fn test_range_is_empty() { + assert!(!(0.0..10.0).is_empty()); + assert!((-0.0..0.0).is_empty()); + assert!((10.0..0.0).is_empty()); + + assert!(!(f32::NEG_INFINITY..f32::INFINITY).is_empty()); + assert!((f32::EPSILON..f32::NAN).is_empty()); + assert!((f32::NAN..f32::EPSILON).is_empty()); + assert!((f32::NAN..f32::NAN).is_empty()); + + assert!(!(0.0..=10.0).is_empty()); + assert!(!(-0.0..=0.0).is_empty()); + assert!((10.0..=0.0).is_empty()); + + assert!(!(f32::NEG_INFINITY..=f32::INFINITY).is_empty()); + assert!((f32::EPSILON..=f32::NAN).is_empty()); + assert!((f32::NAN..=f32::EPSILON).is_empty()); + assert!((f32::NAN..=f32::NAN).is_empty()); +} + +#[test] +fn test_bound_cloned_unbounded() { + assert_eq!(Bound::<&u32>::Unbounded.cloned(), Bound::Unbounded); +} + +#[test] +fn test_bound_cloned_included() { + assert_eq!(Bound::Included(&3).cloned(), Bound::Included(3)); +} + +#[test] +fn test_bound_cloned_excluded() { + assert_eq!(Bound::Excluded(&3).cloned(), Bound::Excluded(3)); +} + +#[test] +#[allow(unused_comparisons)] +#[allow(unused_mut)] +fn test_range_syntax() { + let mut count = 0; + for i in 0_usize..10 { + assert!(i >= 0 && i < 10); + count += i; + } + assert_eq!(count, 45); + + let mut count = 0; + let mut range = 0_usize..10; + for i in range { + assert!(i >= 0 && i < 10); + count += i; + } + assert_eq!(count, 45); + + let mut count = 0; + let mut rf = 3_usize..; + for i in rf.take(10) { + assert!(i >= 3 && i < 13); + count += i; + } + assert_eq!(count, 75); + + let _ = 0_usize..4 + 4 - 3; + + fn foo() -> isize { + 42 + } + let _ = 0..foo(); + + let _ = { &42..&100 }; // references to literals are OK + let _ = ..42_usize; + + // Test we can use two different types with a common supertype. + let x = &42; + { + let y = 42; + let _ = x..&y; + } +} + +#[test] +#[allow(dead_code)] +fn test_range_syntax_in_return_statement() { + fn return_range_to() -> RangeTo { + return ..1; + } + fn return_full_range() -> RangeFull { + return ..; + } + // Not much to test. +} + +#[test] +fn range_structural_match() { + // test that all range types can be structurally matched upon + + const RANGE: Range = 0..1000; + match RANGE { + RANGE => {} + _ => unreachable!(), + } + + const RANGE_FROM: RangeFrom = 0..; + match RANGE_FROM { + RANGE_FROM => {} + _ => unreachable!(), + } + + const RANGE_FULL: RangeFull = ..; + match RANGE_FULL { + RANGE_FULL => {} + } + + const RANGE_INCLUSIVE: RangeInclusive = 0..=999; + match RANGE_INCLUSIVE { + RANGE_INCLUSIVE => {} + _ => unreachable!(), + } + + const RANGE_TO: RangeTo = ..1000; + match RANGE_TO { + RANGE_TO => {} + _ => unreachable!(), + } + + const RANGE_TO_INCLUSIVE: RangeToInclusive = ..=999; + match RANGE_TO_INCLUSIVE { + RANGE_TO_INCLUSIVE => {} + _ => unreachable!(), + } +} + +// Ranges are a nest of one-off errors so... +#[test] +fn test_cmp_range() { + use core::cmp::Ordering::{Equal, Greater, Less}; + + // Range + assert_eq!((50..150).cmp_scalar(40), Some(Greater)); + assert_eq!((50..150).cmp_scalar(49), Some(Greater)); + assert_eq!((50..150).cmp_scalar(50), Some(Equal)); + assert_eq!((50..150).cmp_scalar(51), Some(Equal)); + assert_eq!((50..150).cmp_scalar(100), Some(Equal)); + assert_eq!((50..150).cmp_scalar(149), Some(Equal)); + assert_eq!((50..150).cmp_scalar(150), Some(Less)); + assert_eq!((50..150).cmp_scalar(151), Some(Less)); + assert_eq!((50..150).cmp_scalar(160), Some(Less)); + + // RangeInclusive + assert_eq!((50..=150).cmp_scalar(40), Some(Greater)); + assert_eq!((50..=150).cmp_scalar(49), Some(Greater)); + assert_eq!((50..=150).cmp_scalar(50), Some(Equal)); + assert_eq!((50..=150).cmp_scalar(51), Some(Equal)); + assert_eq!((50..=150).cmp_scalar(100), Some(Equal)); + assert_eq!((50..=150).cmp_scalar(149), Some(Equal)); + assert_eq!((50..=150).cmp_scalar(150), Some(Equal)); + assert_eq!((50..=150).cmp_scalar(151), Some(Less)); + assert_eq!((50..=150).cmp_scalar(160), Some(Less)); + + // RangeFrom + assert_eq!((50..).cmp_scalar(49), Some(Greater)); + assert_eq!((50..).cmp_scalar(50), Some(Equal)); + assert_eq!((50..).cmp_scalar(51), Some(Equal)); + assert_eq!((50..).cmp_scalar(100), Some(Equal)); + assert_eq!((50..).cmp_scalar(u32::MAX), Some(Equal)); + + // RangeTo + assert_eq!((..150).cmp_scalar(u32::MIN), Some(Equal)); + assert_eq!((..150).cmp_scalar(149), Some(Equal)); + assert_eq!((..150).cmp_scalar(150), Some(Less)); + assert_eq!((..150).cmp_scalar(151), Some(Less)); + assert_eq!((..150).cmp_scalar(160), Some(Less)); + + // RangeToInclusive + assert_eq!((..=150).cmp_scalar(u32::MIN), Some(Equal)); + assert_eq!((..=150).cmp_scalar(149), Some(Equal)); + assert_eq!((..=150).cmp_scalar(150), Some(Equal)); + assert_eq!((..=150).cmp_scalar(151), Some(Less)); + assert_eq!((..=150).cmp_scalar(160), Some(Less)); + + // Empty ranges + assert_eq!((50..50).cmp_scalar(49), Some(Greater)); + assert_eq!((50..50).cmp_scalar(50), Some(Less)); + assert_eq!((50..50).cmp_scalar(51), Some(Less)); + + // Almost-empty inclusive ranges + assert_eq!((50..=50).cmp_scalar(49), Some(Greater)); + assert_eq!((50..=50).cmp_scalar(50), Some(Equal)); + assert_eq!((50..=50).cmp_scalar(51), Some(Less)); + + // Degenerate ranges + assert_eq!((20..10).cmp_scalar(15), None); + assert_eq!((20..=10).cmp_scalar(15), None); + + // Uncomparables + assert_eq!((10.0..20.0).cmp_scalar(f32::NAN), None); + assert_eq!((10.0..=20.0).cmp_scalar(f32::NAN), None); + assert_eq!((10.0..).cmp_scalar(f32::NAN), None); + assert_eq!((..20.0).cmp_scalar(f32::NAN), None); + assert_eq!((..=20.0).cmp_scalar(f32::NAN), None); + + assert_eq!((10.0..f32::NAN).cmp_scalar(15.0), None); + assert_eq!((f32::NAN..20.0).cmp_scalar(15.0), None); + assert_eq!((10.0..=f32::NAN).cmp_scalar(15.0), None); + assert_eq!((f32::NAN..=20.0).cmp_scalar(15.0), None); + assert_eq!((f32::NAN..).cmp_scalar(15.0), None); + assert_eq!((..f32::NAN).cmp_scalar(15.0), None); + assert_eq!((..=f32::NAN).cmp_scalar(15.0), None); +}