numerated/
numerated.rs

1// This file is part of Gear.
2
3// Copyright (C) 2023-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! [`Numerated`], [`Bound`] traits definition and implementations for integer types.
20//! Also [`OptionBound`] type is defined, which can be used as [`Bound`] for any type `T: Numerated`.
21
22use core::cmp::Ordering;
23use num_traits::{One, PrimInt, Unsigned};
24
25/// For any type `T`, `Bound<T>` is a type, which has set of values bigger than `T` by one.
26/// - Each value from `T` has unambiguous mapping to `Bound<T>`.
27/// - Each value from `Bound<T>`, except one called __upper__, has unambiguous mapping to `T`.
28/// - __upper__ value has no mapping to `T`, but can be considered as value equal to `T::max_value + 1`.
29///
30/// # Examples
31/// 1) For any `T`, which max value can be get by calling some static live time function,
32///    `Option<T>` can be used as `Bound<T>`. `None` is __upper__. Mapping: Some(t) -> t, t -> Some(t).
33///
34/// 2) When `inner` field max value is always smaller than `inner` type max value, then we can use this variant:
35/// ```
36/// use numerated::Bound;
37///
38/// /// `inner` is a value from 0 to 99.
39/// struct Number { inner: u32 }
40///
41/// /// `inner` is a value from 0 to 100.
42/// #[derive(Clone, Copy)]
43/// struct BoundForNumber { inner: u32 }
44///
45/// impl From<Option<Number>> for BoundForNumber {
46///    fn from(t: Option<Number>) -> Self {
47///       Self { inner: t.map(|t| t.inner).unwrap_or(100) }
48///    }
49/// }
50///
51/// impl Bound<Number> for BoundForNumber {
52///    fn unbound(self) -> Option<Number> {
53///        (self.inner < 100).then_some(Number { inner: self.inner })
54///    }
55/// }
56/// ```
57pub trait Bound<T: Sized>: From<Option<T>> + Copy {
58    /// Unbound means mapping bound back to value if possible.
59    /// - In case bound is __upper__, then returns [`None`].
60    /// - Otherwise returns `Some(p)`, `p: T`.
61    fn unbound(self) -> Option<T>;
62}
63
64/// Numerated type is a type, which has type for distances between any two values of `Self`,
65/// and provide an interface to add/subtract distance to/from value.
66///
67/// Default implementation is provided for all integer types:
68/// [i8] [u8] [i16] [u16] [i32] [u32] [i64] [u64] [i128] [u128] [isize] [usize].
69pub trait Numerated: Copy + Sized + Ord + Eq {
70    /// Numerate type: type that describes the distances between two values of `Self`.
71    type Distance: PrimInt + Unsigned;
72    /// Bound type: type for which any value can be mapped to `Self`,
73    /// and also has __upper__ value, which is bigger than any value of `Self`.
74    type Bound: Bound<Self>;
75    /// Adds `num` to `self`, if `self + num` is enclosed by `self` and `other`.
76    ///
77    /// # Guaranties
78    /// - iff `self + num` is enclosed by `self` and `other`, then returns `Some(_)`.
79    /// - iff `self.add_if_enclosed_by(num, other) == Some(a)`,
80    ///   then `a.sub_if_enclosed_by(num, self) == Some(self)`.
81    fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self>;
82    /// Subtracts `num` from `self`, if `self - num` is enclosed by `self` and `other`.
83    ///
84    /// # Guaranties
85    /// - iff `self - num` is enclosed by `self` and `other`, then returns `Some(_)`.
86    /// - iff `self.sub_if_enclosed_by(num, other) == Some(a)`,
87    ///   then `a.add_if_enclosed_by(num, self) == Some(self)`.
88    fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self>;
89    /// Returns a distance between `self` and `other`
90    ///
91    /// # Guaranties
92    /// - iff `self == other`, then returns `0`.
93    /// - `self.distance(other) == other.distance(self)`.
94    /// - iff `self.distance(other) == a` and `self ≥ other` then
95    ///   - `self.sub_if_enclosed_by(a, other) == Some(other)`
96    ///   - `other.add_if_enclosed_by(a, self) == Some(self)`
97    fn distance(self, other: Self) -> Self::Distance;
98    /// Increments `self`, if `self < other`.
99    fn inc_if_lt(self, other: Self) -> Option<Self> {
100        self.add_if_enclosed_by(Self::Distance::one(), other)
101    }
102    /// Decrements `self`, if `self` > `other`.
103    fn dec_if_gt(self, other: Self) -> Option<Self> {
104        self.sub_if_enclosed_by(Self::Distance::one(), other)
105    }
106    /// Returns `true`, if `self` is enclosed by `a` and `b`.
107    fn enclosed_by(self, a: &Self, b: &Self) -> bool {
108        self <= *a.max(b) && self >= *a.min(b)
109    }
110}
111
112/// Bound type for `Option<T>`.
113#[derive(Clone, Copy, Debug, PartialEq, Eq, derive_more::From)]
114pub struct OptionBound<T>(Option<T>);
115
116impl<T> From<T> for OptionBound<T> {
117    fn from(value: T) -> Self {
118        Some(value).into()
119    }
120}
121
122impl<T: Copy> Bound<T> for OptionBound<T> {
123    fn unbound(self) -> Option<T> {
124        self.0
125    }
126}
127
128impl<T> PartialOrd for OptionBound<T>
129where
130    T: PartialOrd,
131{
132    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133        match (self.0.as_ref(), other.0.as_ref()) {
134            (None, None) => Some(Ordering::Equal),
135            (None, Some(_)) => Some(Ordering::Greater),
136            (Some(_), None) => Some(Ordering::Less),
137            (Some(a), Some(b)) => a.partial_cmp(b),
138        }
139    }
140}
141
142impl<T> Ord for OptionBound<T>
143where
144    T: Ord,
145{
146    fn cmp(&self, other: &Self) -> Ordering {
147        match (self.0.as_ref(), other.0.as_ref()) {
148            (None, None) => Ordering::Equal,
149            (None, Some(_)) => Ordering::Greater,
150            (Some(_), None) => Ordering::Less,
151            (Some(a), Some(b)) => a.cmp(b),
152        }
153    }
154}
155
156impl<T> PartialEq<T> for OptionBound<T>
157where
158    T: PartialEq,
159{
160    fn eq(&self, other: &T) -> bool {
161        self.0.as_ref().map(|a| a.eq(other)).unwrap_or(false)
162    }
163}
164
165impl<T> PartialEq<Option<T>> for OptionBound<T>
166where
167    T: PartialEq,
168{
169    fn eq(&self, other: &Option<T>) -> bool {
170        self.0 == *other
171    }
172}
173
174impl<T> PartialOrd<T> for OptionBound<T>
175where
176    T: PartialOrd,
177{
178    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
179        self.0
180            .as_ref()
181            .map(|a| a.partial_cmp(other))
182            .unwrap_or(Some(Ordering::Greater))
183    }
184}
185
186macro_rules! impl_for_unsigned {
187    ($($t:ty)*) => ($(
188        impl Numerated for $t {
189            type Distance = $t;
190            type Bound = OptionBound<$t>;
191            fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
192                self.checked_add(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res))
193            }
194            fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
195                self.checked_sub(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res))
196            }
197            fn distance(self, other: Self) -> $t {
198                self.abs_diff(other)
199            }
200        }
201    )*)
202}
203
204impl_for_unsigned!(u8 u16 u32 u64 u128 usize);
205
206macro_rules! impl_for_signed {
207    ($($s:ty => $u:ty),*) => {
208        $(
209            impl Numerated for $s {
210                type Distance = $u;
211                type Bound = OptionBound<$s>;
212                fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
213                    let res = (self as $u).wrapping_add(num) as $s;
214                    res.enclosed_by(&self, &other).then_some(res)
215                }
216                fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
217                    let res = (self as $u).wrapping_sub(num) as $s;
218                    res.enclosed_by(&self, &other).then_some(res)
219                }
220                fn distance(self, other: Self) -> $u {
221                    self.abs_diff(other)
222                }
223            }
224        )*
225    };
226}
227
228impl_for_signed!(i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128, isize => usize);
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn test_option_bound() {
236        let a = OptionBound::from(1);
237        let b = OptionBound::from(2);
238        let c = OptionBound::from(None);
239        assert_eq!(a.unbound(), Some(1));
240        assert_eq!(b.unbound(), Some(2));
241        assert_eq!(c.unbound(), None);
242        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
243        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
244        assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
245        assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));
246        assert_eq!(c.partial_cmp(&c), Some(Ordering::Equal));
247        assert_eq!(a.partial_cmp(&2), Some(Ordering::Less));
248        assert_eq!(b.partial_cmp(&2), Some(Ordering::Equal));
249        assert_eq!(c.partial_cmp(&2), Some(Ordering::Greater));
250        assert_eq!(a, 1);
251        assert_eq!(b, 2);
252        assert_eq!(c, None);
253        assert_eq!(a, Some(1));
254        assert_eq!(b, Some(2));
255    }
256
257    #[test]
258    fn test_u8() {
259        let a = 1u8;
260        let b = 2u8;
261        assert_eq!(a.add_if_enclosed_by(1, b), Some(2));
262        assert_eq!(a.sub_if_enclosed_by(1, b), None);
263        assert_eq!(a.distance(b), 1);
264        assert_eq!(a.inc_if_lt(b), Some(2));
265        assert_eq!(a.dec_if_gt(b), None);
266    }
267
268    #[test]
269    fn test_i8() {
270        let a = -1i8;
271        let b = 1i8;
272        assert_eq!(a.add_if_enclosed_by(2, b), Some(1));
273        assert_eq!(a.sub_if_enclosed_by(1, b), None);
274        assert_eq!(a.distance(b), 2);
275        assert_eq!(a.inc_if_lt(b), Some(0));
276        assert_eq!(a.dec_if_gt(b), None);
277    }
278}