use core::{
marker::PhantomData,
ops::{Add, Sub},
};
use generic_array::ArrayLength;
use crate::{
Finite, FitsInUsize,
typenum::{B1, Unsigned},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InRange<A: Unsigned, B: Unsigned>
where
B: Sub<A>,
<B as Sub<A>>::Output: Unsigned,
{
value: usize,
_phantom: PhantomData<(A, B)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InRangeInclusive<A: Unsigned, B: Unsigned>
where
B: Sub<A>,
<B as Sub<A>>::Output: Add<B1>,
<<B as Sub<A>>::Output as Add<B1>>::Output: ArrayLength,
{
value: usize,
_phantom: PhantomData<(A, B)>,
}
pub trait InRangeBounds: Copy + Sized {
type MIN: Unsigned;
type INHABITANTS: ArrayLength + FitsInUsize;
#[must_use]
unsafe fn new_unchecked(i: usize) -> Self;
#[must_use]
fn get(self) -> usize;
#[must_use]
fn new_from_start_offset(offset: usize) -> Option<Self> {
Self::new(Self::MIN::USIZE + offset)
}
#[must_use]
fn offset_from_start(i: usize) -> Option<usize> {
let offset = i.checked_sub(Self::MIN::USIZE)?;
if offset < Self::INHABITANTS::USIZE {
Some(offset)
} else {
None
}
}
#[must_use]
fn in_bounds(i: usize) -> bool {
Self::offset_from_start(i).is_some()
}
#[must_use]
fn new(i: usize) -> Option<Self> {
if Self::in_bounds(i) {
Some(unsafe { Self::new_unchecked(i) })
} else {
None
}
}
}
impl<A: Unsigned, B: Unsigned> InRangeBounds for InRange<A, B>
where
B: Sub<A>,
<B as Sub<A>>::Output: ArrayLength + FitsInUsize,
{
type MIN = A;
type INHABITANTS = <B as Sub<A>>::Output;
unsafe fn new_unchecked(i: usize) -> Self {
Self {
value: i,
_phantom: PhantomData,
}
}
fn get(self) -> usize {
self.value
}
}
impl<A: Unsigned, B: Unsigned> InRangeBounds for InRangeInclusive<A, B>
where
B: Sub<A>,
<B as Sub<A>>::Output: Add<B1>,
<<B as Sub<A>>::Output as Add<B1>>::Output: ArrayLength + FitsInUsize,
{
type MIN = A;
type INHABITANTS = <<B as Sub<A>>::Output as Add<B1>>::Output;
unsafe fn new_unchecked(i: usize) -> Self {
Self {
value: i,
_phantom: PhantomData,
}
}
fn get(self) -> usize {
self.value
}
}
impl<A: Unsigned, B: Unsigned> Finite for InRange<A, B>
where
B: Sub<A>,
<B as Sub<A>>::Output: ArrayLength + FitsInUsize,
{
type INHABITANTS = <Self as InRangeBounds>::INHABITANTS;
fn to_usize(&self) -> usize {
self.get() - <Self as InRangeBounds>::MIN::USIZE
}
fn from_usize(i: usize) -> Option<Self> {
Self::new_from_start_offset(i)
}
}
impl<A: Unsigned, B: Unsigned> Finite for InRangeInclusive<A, B>
where
B: Sub<A>,
<B as Sub<A>>::Output: Add<B1>,
<<B as Sub<A>>::Output as Add<B1>>::Output: ArrayLength + FitsInUsize,
{
type INHABITANTS = <Self as InRangeBounds>::INHABITANTS;
fn to_usize(&self) -> usize {
self.get() - <Self as InRangeBounds>::MIN::USIZE
}
fn from_usize(i: usize) -> Option<Self> {
Self::new_from_start_offset(i)
}
}
#[cfg(all(test, feature = "std"))]
mod test {
use std::{fmt::Debug, ops::RangeBounds};
use super::*;
use crate::typenum::{Pow, Sub1, U, U0, U1, U3, U256};
type UsizeMax = Sub1<<U256 as Pow<U<{ std::mem::size_of::<usize>() }>>>::Output>;
fn test_range<T: InRangeBounds + Debug + PartialEq, R: RangeBounds<usize>>(expected_range: R) {
for i in (0..10).chain(usize::MAX - 10..=usize::MAX) {
let v = T::new(i);
if expected_range.contains(&i) {
assert_eq!(v.map(InRangeBounds::get), Some(i));
} else {
assert_eq!(v, None);
}
}
}
#[test]
fn test_in_range_full() {
test_range::<InRange<U0, UsizeMax>, _>(0..usize::MAX);
}
#[test]
fn test_in_range_inclusive_almost_full() {
test_range::<InRangeInclusive<U1, UsizeMax>, _>(1..=usize::MAX);
}
#[test]
fn test_in_range() {
test_range::<InRange<U1, U3>, _>(1..3);
}
#[test]
fn test_in_range_inclusive() {
test_range::<InRangeInclusive<U1, U3>, _>(1..=3);
}
}