use core::{
cmp::Ordering,
fmt::Debug,
hash::{Hash, Hasher},
mem,
ops::RangeInclusive,
slice::from_raw_parts,
};
use super::not_nan_float::NotNanFloat;
use crate::Integer;
#[cfg(feature = "from_slice")]
use crate::RangeSetBlaze;
use num_traits::Zero;
pub type NotNanF64 = NotNan<f64>;
pub type NotNanF32 = NotNan<f32>;
#[cfg(feature = "float_nightly_experimental")]
pub type NotNanF16 = NotNan<f16>;
#[cfg(feature = "float_nightly_experimental")]
pub type NotNanF128 = NotNan<f128>;
#[must_use]
pub const fn nnf64(x: f64) -> NotNanF64 {
not_nan_f64(x)
}
#[must_use]
pub const fn nnf32(x: f32) -> NotNanF32 {
not_nan_f32(x)
}
#[cfg(feature = "float_nightly_experimental")]
#[must_use]
pub const fn nnf16(x: f16) -> NotNanF16 {
not_nan_f16(x)
}
#[cfg(feature = "float_nightly_experimental")]
#[must_use]
pub const fn nnf128(x: f128) -> NotNanF128 {
not_nan_f128(x)
}
macro_rules! not_nan_const_constructor {
($name:ident, $primitive:ty, $not_nan:ty) => {
const fn $name(x: $primitive) -> $not_nan {
assert!(!x.is_nan(), "NotNan type requires a non-NaN value");
let normalized = if x == 0.0 && x.is_sign_negative() {
0.0
} else {
x
};
NotNan(normalized)
}
};
}
not_nan_const_constructor!(not_nan_f64, f64, NotNanF64);
not_nan_const_constructor!(not_nan_f32, f32, NotNanF32);
#[cfg(feature = "float_nightly_experimental")]
not_nan_const_constructor!(not_nan_f16, f16, NotNanF16);
#[cfg(feature = "float_nightly_experimental")]
not_nan_const_constructor!(not_nan_f128, f128, NotNanF128);
#[repr(transparent)]
#[derive(Copy, Clone, Default, Debug)]
pub struct NotNan<T: NotNanFloat>(T);
impl<T: NotNanFloat> NotNan<T> {
pub const MIN: Self = Self(T::MIN);
pub const MAX: Self = Self(T::MAX);
pub const MAX_SIZE: T::SafeLen = T::MAX_SIZE;
#[must_use]
pub fn new(x: T) -> Self {
Self::try_new(x).expect("NotNan type requires a non-NaN value")
}
#[must_use]
pub fn try_new(x: T) -> Option<Self> {
(!T::is_nan(x)).then(|| unsafe { Self::new_unchecked(T::normalize(x)) })
}
#[must_use]
pub const unsafe fn new_unchecked(x: T) -> Self {
Self(x)
}
#[must_use]
pub fn inclusive_end_from_start(self, b: T::SafeLen) -> Self {
let max_len = T::prim_safe_len(self.0, T::MAX);
assert!(
!b.is_zero() && b <= max_len,
"b must be in range 1..=max_len"
);
Self(T::inclusive_end_from_start(self.0, b))
}
#[must_use]
pub fn start_from_inclusive_end(self, b: T::SafeLen) -> Self {
let max_len = T::prim_safe_len(T::MIN, self.0);
assert!(
!b.is_zero() && b <= max_len,
"b must be in range 1..=max_len"
);
Self(T::start_from_inclusive_end(self.0, b))
}
#[must_use]
pub const fn into_inner(self) -> T {
self.0
}
#[must_use]
pub fn after(self) -> Self {
assert!(self != Self::MAX, "after() called on maximum value");
Self(T::normalize(T::after(self.0)))
}
#[must_use]
pub fn before(self) -> Self {
assert!(self != Self::MIN, "before() called on minimum value");
Self(T::normalize(T::before(self.0)))
}
#[must_use]
pub fn checked_after(self) -> Option<Self> {
if self == Self::MAX {
None
} else {
Some(self.after())
}
}
#[must_use]
pub fn checked_before(self) -> Option<Self> {
if self == Self::MIN {
None
} else {
Some(self.before())
}
}
#[must_use]
pub fn from_primitive_range(range: RangeInclusive<T>) -> RangeInclusive<Self> {
let (start, end) = range.into_inner();
Self::new(start)..=Self::new(end)
}
pub fn from_primitive_ranges<I>(ranges: I) -> impl Iterator<Item = RangeInclusive<Self>>
where
I: IntoIterator<Item = RangeInclusive<T>>,
{
ranges.into_iter().map(Self::from_primitive_range)
}
pub fn values<I>(values: I) -> impl Iterator<Item = Self>
where
I: IntoIterator<Item = T>,
{
values.into_iter().map(Self::new)
}
#[must_use]
pub fn from_primitive_slice(values: &[T]) -> &[Self] {
assert!(
values.iter().all(|&v| !T::is_nan(v) && !T::is_neg_zero(v)),
"NotNan type requires non-NaN, non-negative-zero values"
);
unsafe { Self::from_primitive_slice_unchecked(values) }
}
#[must_use]
pub const unsafe fn from_primitive_slice_unchecked(values: &[T]) -> &[Self] {
unsafe { mem::transmute::<&[T], &[Self]>(values) }
}
}
pub trait NotNanSliceExt<T: NotNanFloat> {
fn as_primitive_slice(&self) -> &[T];
}
impl<T: NotNanFloat> NotNanSliceExt<T> for [NotNan<T>] {
fn as_primitive_slice(&self) -> &[T] {
unsafe { from_raw_parts(self.as_ptr().cast::<T>(), self.len()) }
}
}
pub trait NotNanRangeExt<T: NotNanFloat> {
#[must_use]
fn into_primitive_range(self) -> RangeInclusive<T>;
#[must_use]
fn into_primitive_inner(self) -> (T, T);
}
impl<T: NotNanFloat> NotNanRangeExt<T> for RangeInclusive<NotNan<T>> {
fn into_primitive_range(self) -> RangeInclusive<T> {
let (start, end) = self.into_primitive_inner();
start..=end
}
fn into_primitive_inner(self) -> (T, T) {
let (start, end) = self.into_inner();
(start.into_inner(), end.into_inner())
}
}
impl<T: NotNanFloat> PartialEq for NotNan<T> {
fn eq(&self, other: &Self) -> bool {
T::total_cmp(self.0, other.0) == Ordering::Equal
}
}
impl<T: NotNanFloat> Eq for NotNan<T> {}
impl<T: NotNanFloat> PartialOrd for NotNan<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: NotNanFloat> Ord for NotNan<T> {
fn cmp(&self, other: &Self) -> Ordering {
T::total_cmp(self.0, other.0)
}
}
impl<T: NotNanFloat> Hash for NotNan<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
T::hash(self.0, state);
}
}
impl<T: NotNanFloat> Integer for NotNan<T> {
type SafeLen = T::SafeLen;
#[inline]
fn checked_add_one(self) -> Option<Self> {
self.checked_after()
}
#[inline]
fn add_one(self) -> Self {
self.after()
}
#[inline]
fn sub_one(self) -> Self {
self.before()
}
#[inline]
fn assign_sub_one(&mut self) {
*self = self.before();
}
#[inline]
fn range_next(range: &mut RangeInclusive<Self>) -> Option<Self> {
if range.is_empty() {
None
} else if range.start() == range.end() && *range.start() == Self::MAX {
let next = *range.start();
*range = next..=range.end().before();
Some(next)
} else {
let next = *range.start();
*range = (next.after())..=*range.end();
Some(next)
}
}
#[inline]
fn range_next_back(range: &mut RangeInclusive<Self>) -> Option<Self> {
if range.is_empty() {
None
} else if range.start() == range.end() && *range.start() == Self::MIN {
let last = *range.end();
*range = last.after()..=last;
Some(last)
} else {
let last = *range.end();
*range = *range.start()..=last.before();
Some(last)
}
}
#[inline]
fn min_value() -> Self {
Self::MIN
}
#[inline]
fn max_value() -> Self {
Self::MAX
}
#[cfg(feature = "from_slice")]
#[inline]
fn from_slice(slice: impl AsRef<[Self]>) -> RangeSetBlaze<Self> {
RangeSetBlaze::from_iter(slice.as_ref())
}
fn safe_len(r: &RangeInclusive<Self>) -> Self::SafeLen {
let (start, end) = r.clone().into_primitive_inner();
T::prim_safe_len(start, end)
}
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64 {
T::safe_len_to_f64_lossy(len)
}
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen {
T::f64_to_safe_len_lossy(f)
}
fn inclusive_end_from_start(self, b: Self::SafeLen) -> Self {
self.inclusive_end_from_start(b)
}
fn start_from_inclusive_end(self, b: Self::SafeLen) -> Self {
self.start_from_inclusive_end(b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Integer;
#[cfg(not(target_arch = "wasm32"))]
use std::hint::black_box;
#[cfg(not(target_arch = "wasm32"))]
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::vec;
use std::vec::Vec;
#[cfg(not(target_arch = "wasm32"))]
fn panics(f: impl FnOnce()) -> bool {
catch_unwind(AssertUnwindSafe(f)).is_err()
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
#[allow(clippy::float_cmp)]
fn safe_constructors_preserve_not_nan_invariant() {
assert_eq!(nnf32(-0.0).into_inner().to_bits(), 0);
assert_eq!(nnf64(-0.0).into_inner().to_bits(), 0);
assert_eq!(NotNanF64::new(-0.0), nnf64(0.0));
assert_eq!(NotNanF64::try_new(-0.0), Some(nnf64(0.0)));
for value in [f64::INFINITY, f64::NEG_INFINITY] {
assert_eq!(NotNanF64::new(value).into_inner(), value);
assert_eq!(NotNanF64::try_new(value), Some(NotNanF64::new(value)));
}
for invalid in [f64::NAN, -f64::NAN] {
assert!(panics(|| {
black_box(NotNanF64::new(invalid));
}));
assert_eq!(NotNanF64::try_new(invalid), None);
assert!(panics(|| drop(NotNanF64::from_primitive_range(
invalid..=1.0
))));
assert!(panics(|| {
NotNanF64::values([invalid]).count();
}));
assert!(panics(|| {
black_box(NotNanF64::from_primitive_slice(&[invalid]));
}));
}
assert!(panics(|| {
black_box(NotNanF64::from_primitive_slice(&[-0.0]));
}));
assert!(panics(|| {
black_box(NotNanF64::from_primitive_slice(&[f64::NAN]));
}));
let values = [1.0, 2.0, 3.0];
let not_nans = NotNanF64::from_primitive_slice(&values);
assert_eq!(not_nans.as_primitive_slice(), &values);
assert_eq!(
NotNanF64::values(values).collect::<Vec<_>>(),
vec![nnf64(1.0), nnf64(2.0), nnf64(3.0)]
);
assert_eq!(
NotNanF64::from_primitive_ranges([1.0..=2.0]).collect::<Vec<_>>(),
vec![nnf64(1.0)..=nnf64(2.0)]
);
}
#[test]
fn ordering_agrees_with_total_cmp() {
let values = [
f64::NEG_INFINITY,
-f64::MAX,
-1.0,
0.0,
1.0,
f64::MAX,
f64::INFINITY,
];
for left in values {
for right in values {
assert_eq!(nnf64(left).cmp(&nnf64(right)), left.total_cmp(&right));
}
}
assert_ne!(nnf64(0.0).cmp(&nnf64(-0.0)), 0.0_f64.total_cmp(&-0.0));
}
#[test]
fn converts_ranges() {
assert_eq!(
NotNanF64::from_primitive_range(10.0..=20.0),
nnf64(10.0)..=nnf64(20.0)
);
assert_eq!(
NotNanF64::from_primitive_ranges([10.0..=20.0, 30.0..=40.0]).collect::<Vec<_>>(),
vec![nnf64(10.0)..=nnf64(20.0), nnf64(30.0)..=nnf64(40.0)]
);
}
#[test]
fn after_and_before_step_through_zero_in_total_order() {
assert_eq!(nnf64(-0.0), nnf64(0.0));
assert_ne!(nnf64(0.0).before(), nnf64(-0.0));
assert_eq!(nnf64(0.0).after(), nnf64(f64::from_bits(1)));
assert_eq!(
nnf64(0.0).before(),
nnf64(f64::from_bits(0x8000_0000_0000_0001))
);
}
#[test]
fn after_and_before_panic_at_boundaries_in_all_build_modes() {
assert_eq!(NotNanF64::MAX.checked_after(), None);
assert_eq!(NotNanF64::MIN.checked_before(), None);
}
#[test]
#[should_panic(expected = "b must be in range 1..=max_len")]
fn not_nan_endpoint_offset_cannot_leave_domain() {
let _ = NotNanF32::MAX.inclusive_end_from_start(2);
}
#[test]
#[should_panic(expected = "after() called on maximum value")]
fn after_panics_at_max() {
let _ = NotNanF64::MAX.after();
}
#[test]
#[should_panic(expected = "before() called on minimum value")]
fn before_panics_at_min() {
let _ = NotNanF64::MIN.before();
}
#[test]
fn checked_after_and_before_stop_at_total_order_boundaries() {
assert_eq!(NotNanF64::MIN.checked_before(), None);
assert_eq!(NotNanF64::MAX.checked_after(), None);
assert_eq!(NotNanF64::MIN.checked_after(), Some(NotNanF64::MIN.after()));
assert_eq!(
NotNanF64::MAX.checked_before(),
Some(NotNanF64::MAX.before())
);
}
#[test]
fn min_and_max_are_total_order_boundaries() {
let values = [
nnf64(-f64::MAX),
nnf64(-1.0),
nnf64(-0.0),
nnf64(0.0),
nnf64(1.0),
nnf64(f64::MAX),
];
for value in values {
assert!(NotNanF64::MIN <= value);
assert!(value <= NotNanF64::MAX);
}
}
#[test]
fn infinities_are_adjacent_to_finite_extremes() {
assert_eq!(NotNanF64::MIN, nnf64(f64::NEG_INFINITY));
assert_eq!(NotNanF64::MAX, nnf64(f64::INFINITY));
assert_eq!(NotNanF64::MIN.after(), nnf64(f64::MIN));
assert_eq!(NotNanF64::MAX.before(), nnf64(f64::MAX));
assert_eq!(nnf64(f64::MIN).before(), NotNanF64::MIN);
assert_eq!(nnf64(f64::MAX).after(), NotNanF64::MAX);
assert_eq!(NotNanF32::MIN, nnf32(f32::NEG_INFINITY));
assert_eq!(NotNanF32::MAX, nnf32(f32::INFINITY));
assert_eq!(NotNanF32::MIN.after(), nnf32(f32::MIN));
assert_eq!(NotNanF32::MAX.before(), nnf32(f32::MAX));
assert_eq!(nnf32(f32::MIN).before(), NotNanF32::MIN);
assert_eq!(nnf32(f32::MAX).after(), NotNanF32::MAX);
}
#[test]
fn infinities_are_valid_range_endpoints() {
use crate::RangeSetBlaze;
let set = RangeSetBlaze::from_iter([nnf64(f64::NEG_INFINITY)..=nnf64(0.0)]);
assert!(set.contains(NotNanF64::MIN));
assert!(set.contains(nnf64(f64::MIN)));
assert!(set.contains(nnf64(0.0)));
assert!(!set.contains(nnf64(0.0).after()));
assert!(!set.contains(NotNanF64::MAX));
let full = !RangeSetBlaze::<NotNanF64>::new();
assert!(full.contains(NotNanF64::MIN));
assert!(full.contains(NotNanF64::MAX));
assert_eq!(full.len(), NotNanF64::MAX_SIZE);
}
#[test]
fn after_and_before_are_neighbors_in_total_order() {
let values = [
NotNanF64::MIN,
nnf64(f64::MIN),
nnf64(-f64::MAX),
nnf64(-1.0),
nnf64(-0.0),
nnf64(0.0),
nnf64(1.0),
nnf64(f64::MAX),
NotNanF64::MAX,
];
for value in values {
if value != NotNanF64::MAX {
assert_eq!(value.after().before(), value);
}
if value != NotNanF64::MIN {
assert_eq!(value.before().after(), value);
}
}
}
#[test]
fn adjacency_laws_cover_f32_and_f64_edges() {
macro_rules! check {
($name:ident, $zero:expr, $negative_subnormal:expr, $positive_subnormal:expr, $min:expr, $max:expr) => {{
let values = [
nnf32($zero),
nnf32($negative_subnormal),
nnf32($positive_subnormal),
nnf32(-1.0),
nnf32(1.0),
nnf32($min),
nnf32($max),
];
for value in values {
if value != NotNanF32::MAX {
assert_eq!(value.after().before(), value);
}
if value != NotNanF32::MIN {
assert_eq!(value.before().after(), value);
}
}
assert_eq!(NotNanF32::MIN.checked_before(), None);
assert_eq!(NotNanF32::MAX.checked_after(), None);
assert_eq!(nnf32($negative_subnormal).after(), nnf32($zero));
assert_eq!(nnf32($zero).after(), nnf32($positive_subnormal));
let _ = stringify!($name);
}};
}
check!(
f32_edges,
0.0_f32,
-f32::from_bits(1),
f32::from_bits(1),
f32::MIN,
f32::MAX
);
let values = [
nnf64(-f64::from_bits(1)),
nnf64(0.0),
nnf64(f64::from_bits(1)),
nnf64(-1.0),
nnf64(1.0),
NotNanF64::MIN,
NotNanF64::MAX,
];
for value in values {
if value != NotNanF64::MAX {
assert_eq!(value.after().before(), value);
}
if value != NotNanF64::MIN {
assert_eq!(value.before().after(), value);
}
}
assert_eq!(NotNanF64::MIN.checked_before(), None);
assert_eq!(NotNanF64::MAX.checked_after(), None);
assert_eq!(nnf64(-f64::from_bits(1)).after(), nnf64(0.0));
assert_eq!(nnf64(0.0).after(), nnf64(f64::from_bits(1)));
}
#[test]
fn range_length_laws_cover_f32_and_f64() {
let start = nnf32(-f32::from_bits(1));
let end = nnf32(f32::from_bits(1));
assert_eq!(NotNanF32::safe_len(&(start..=start)), 1);
assert_eq!(NotNanF32::safe_len(&(start..=start.after())), 2);
assert_eq!(NotNanF32::safe_len(&(start..=end)), 3);
assert_eq!(
NotNanF32::MAX_SIZE,
NotNanF32::safe_len(&(NotNanF32::MIN..=NotNanF32::MAX))
);
let length = 17;
let endpoint = start.inclusive_end_from_start(length);
assert_eq!(endpoint.start_from_inclusive_end(length), start);
assert_eq!(start.inclusive_end_from_start(length), endpoint);
let start = nnf64(-f64::from_bits(1));
let end = nnf64(f64::from_bits(1));
assert_eq!(NotNanF64::safe_len(&(start..=start)), 1);
assert_eq!(NotNanF64::safe_len(&(start..=start.after())), 2);
assert_eq!(NotNanF64::safe_len(&(start..=end)), 3);
assert_eq!(
NotNanF64::MAX_SIZE,
NotNanF64::safe_len(&(NotNanF64::MIN..=NotNanF64::MAX))
);
let length = 17;
let endpoint = start.inclusive_end_from_start(length);
assert_eq!(endpoint.start_from_inclusive_end(length), start);
assert_eq!(start.inclusive_end_from_start(length), endpoint);
}
#[cfg(feature = "float_nightly_experimental")]
#[test]
fn f16_not_nan_adjacency_and_lengths_are_exhaustive() {
for bits in 0..=u16::MAX {
let value = f16::from_bits(bits);
let Some(value) = NotNanF16::try_new(value) else {
continue;
};
if value != NotNanF16::MIN {
assert_eq!(value.before().after(), value);
}
if value != NotNanF16::MAX {
assert_eq!(value.after().before(), value);
}
assert_eq!(NotNanF16::safe_len(&(value..=value)), 1);
}
assert_eq!(
NotNanF16::MAX_SIZE,
NotNanF16::safe_len(&(NotNanF16::MIN..=NotNanF16::MAX))
);
}
}