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}