wrapnum/
lib.rs

1//! Ever wanted to just make a number and have it automatically wrap around an
2//! arbitrary minimum/maximum without ever thinking about it after creating the number?
3//! Here you go.
4//!
5//! The goal of this is to act as much like any other integer type available, so that you can just
6//! think of this as a number and leave all the wrapping to us!
7//!
8//! # Notes
9//! This library uses logic that does not change between debug and release modes, unlike some
10//! methods like [`std::intrinsics::wrapping_add()`]. As such, this library is not meant to be
11//! performance critical; it is simply meant to be a "one-and-done forget about it" variable.
12
13use std::{
14    fmt::Display,
15    ops::{Add, AddAssign, Index, IndexMut, Rem, Sub, SubAssign},
16};
17
18use num::{zero, ToPrimitive};
19
20macro_rules! impl_from_wrapnum {
21    ($($t:ty),*) => {
22        $(
23            impl From<WrapNum<$t>> for $t {
24                fn from(wrap_num: WrapNum<$t>) -> Self {
25                    wrap_num.value
26                }
27            }
28        )*
29    };
30}
31
32#[derive(Clone, Copy, Debug)]
33/// Number with arbitrary wrapping.
34pub struct WrapNum<T> {
35    /// Current value.
36    pub value: T,
37    /// Minimum value.
38    pub min: T,
39    /// Maximimum value.
40    pub max: T,
41}
42
43impl<T> Display for WrapNum<T>
44where
45    T: std::fmt::Display,
46{
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "{}", self.value)
49    }
50}
51
52impl<T> PartialEq for WrapNum<T>
53where
54    T: Copy + PartialEq,
55{
56    fn eq(&self, other: &Self) -> bool {
57        self.value == other.value
58    }
59}
60
61impl<T> PartialEq<T> for WrapNum<T>
62where
63    T: Copy + PartialEq,
64{
65    fn eq(&self, other: &T) -> bool {
66        self.value == *other
67    }
68}
69
70impl<T, U> Index<WrapNum<U>> for Vec<T>
71where
72    U: ToPrimitive + Copy,
73{
74    type Output = T;
75
76    fn index(&self, index: WrapNum<U>) -> &Self::Output {
77        let idx = index
78            .value
79            .to_usize()
80            .expect("Failed to convert index to usize");
81        &self[idx]
82    }
83}
84
85impl<T, U> IndexMut<WrapNum<U>> for Vec<T>
86where
87    U: ToPrimitive + Copy,
88{
89    fn index_mut(&mut self, index: WrapNum<U>) -> &mut Self::Output {
90        &mut self[index
91            .value
92            .to_usize()
93            .expect("Failed to convert index to usize")]
94    }
95}
96
97impl<T> WrapNum<T>
98where
99    T: Add<Output = T> + Sub<Output = T> + Ord + num::Bounded + Rem<Output = T> + Copy,
100{
101    fn wrapped_result(value: T, min: T, max: T) -> T {
102        let range = max - min;
103        (value - min) % range + min
104    }
105}
106
107impl<T> Add for WrapNum<T>
108where
109    T: Add<Output = T> + Sub<Output = T> + Ord + num::Bounded + Rem<Output = T> + Copy,
110{
111    type Output = Self;
112
113    fn add(self, rhs: Self) -> Self::Output {
114        let wrapped_value = Self::wrapped_result(self.value + rhs.value, self.min, self.max);
115
116        Self {
117            value: wrapped_value,
118            min: self.min,
119            max: self.max,
120        }
121    }
122}
123
124impl<T> Add<T> for WrapNum<T>
125where
126    T: Add<Output = T> + Sub<Output = T> + Ord + num::Bounded + Rem<Output = T> + Copy,
127{
128    type Output = Self;
129
130    fn add(self, rhs: T) -> Self::Output {
131        let wrapped_value = Self::wrapped_result(self.value + rhs, self.min, self.max);
132
133        Self {
134            value: wrapped_value,
135            min: self.min,
136            max: self.max,
137        }
138    }
139}
140
141impl<T> Sub for WrapNum<T>
142where
143    T: Sub<Output = T> + Add<Output = T> + Rem<Output = T> + Ord + num::Bounded + num::One + Copy,
144{
145    type Output = Self;
146
147    fn sub(self, rhs: Self) -> Self::Output {
148        let result = if self.value < rhs.value {
149            self.max - self.min + (self.value - rhs.value)
150        } else {
151            self.value - rhs.value
152        };
153
154        let wrapped_value = Self::wrapped_result(result, self.min, self.max);
155
156        Self {
157            value: wrapped_value,
158            min: self.min,
159            max: self.max,
160        }
161    }
162}
163
164impl<T> Sub<T> for WrapNum<T>
165where
166    T: Sub<Output = T> + Add<Output = T> + Rem<Output = T> + Ord + num::Bounded + num::One + Copy,
167{
168    type Output = Self;
169
170    fn sub(self, rhs: T) -> Self::Output {
171        let result = if self.value < rhs {
172            self.max - self.min + (self.value - rhs)
173        } else {
174            self.value - rhs
175        };
176
177        let wrapped_value = Self::wrapped_result(result, self.min, self.max);
178
179        Self {
180            value: wrapped_value,
181            min: self.min,
182            max: self.max,
183        }
184    }
185}
186
187impl<T> AddAssign<T> for WrapNum<T>
188where
189    T: Add<Output = T> + Sub<Output = T> + Ord + num::Bounded + Rem<Output = T> + Copy,
190{
191    fn add_assign(&mut self, rhs: T) {
192        let result = self.value + rhs;
193
194        self.value = Self::wrapped_result(result, self.min, self.max);
195    }
196}
197
198impl<T> SubAssign<T> for WrapNum<T>
199where
200    T: Sub<Output = T> + Add<Output = T> + Rem<Output = T> + Ord + num::Bounded + num::One + Copy,
201{
202    fn sub_assign(&mut self, rhs: T) {
203        let result = if self.value < rhs {
204            self.max - self.min + (self.value - rhs)
205        } else {
206            self.value - rhs
207        };
208
209        self.value = Self::wrapped_result(result, self.min, self.max);
210    }
211}
212
213impl<T> From<T> for WrapNum<T>
214where
215    T: Copy + num::Bounded + num::Zero,
216{
217    fn from(value: T) -> Self {
218        Self {
219            value,
220            min: zero(),
221            max: T::max_value(),
222        }
223    }
224}
225
226//  The reason why we can't just make one generic implementation is because I believe we need a
227//  real type on the righthandside of the "for".
228impl_from_wrapnum!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
229
230impl<T> Default for WrapNum<T>
231where
232    T: num::Bounded + num::Zero,
233{
234    /// The default behavior is to set [`WrapNum::value`] and [`WrapNum::min`] to [`zero()`] and
235    /// [`WrapNum::max`] to [`num::Bounded::max_value()`].
236    fn default() -> Self {
237        WrapNum {
238            value: zero(),
239            min: zero(),
240            max: T::max_value(),
241        }
242    }
243}
244
245impl<T> WrapNum<T>
246where
247    T: num::Bounded + num::Zero + PartialOrd,
248{
249    /// Create new wrapped number and automatic zeroed [`WrapNum::value`].
250    pub fn new(max: T) -> Self {
251        Self {
252            max,
253            ..Default::default()
254        }
255    }
256
257    /// Create new wrapped number with given max.
258    ///
259    /// # Panics
260    /// This will panic if `value > max`.
261    pub fn new_max(value: T, max: T) -> Self {
262        assert!(!(value > max), "`value` is greater than `max`.");
263        Self {
264            value,
265            max,
266            ..Default::default()
267        }
268    }
269
270    /// Create new wrapped number with given min/max.
271    ///
272    /// # Panics
273    /// This will panic if `value > max` or `value < min`.
274    pub fn new_min_max(value: T, min: T, max: T) -> Self {
275        if value > max {
276            panic!("`value` is greater than `max`.");
277        } else if value < min {
278            panic!("`value` is less than `min`.");
279        }
280        Self { value, min, max }
281    }
282}
283
284impl<T: PartialEq> WrapNum<T> {
285    pub fn total_eq(self, other: &Self) -> bool {
286        self.value == other.value && self.min == other.min && self.max == other.max
287    }
288}
289
290#[macro_export]
291/// Create [`WrapNum`] with value, minimum and maximum.
292///
293/// # Running
294/// 1. With one value passed, which will set that as the maximum and default everything else to
295///    `0`.
296/// 2. With one inclusive value passed (`=5`) and defaults everything else as above.
297/// 3. With a value and a maximum.
298/// 4. With a value, a minimum, and a maximum.
299/// 5. With a range passed (`5..30`).
300/// 6. With an inclusive range passed (`5..=30`).
301macro_rules! wrap {
302    ($max:expr) => {
303        $crate::WrapNum::new($max)
304    };
305    (=$max:expr) => {
306        $crate::WrapNum::new($max + 1)
307    };
308    ($v:expr, $max:expr) => {
309        $crate::WrapNum::new_max($v, $max)
310    };
311    (($min:expr)..($max:expr)) => {
312        $crate::WrapNum::new_min_max($min, $min, $max)
313    };
314    (($min:expr)..=($max:expr)) => {
315        $crate::WrapNum::new_min_max($min, $min, $max + 1)
316    };
317    ($v:expr, $min:expr, $max:expr) => {
318        $crate::WrapNum::new_min_max($v, $min, $max)
319    };
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn make_usize() {
328        let mut result = wrap!(50);
329        for _ in 0..60 {
330            result += 1;
331            println!("{}", result);
332        }
333    }
334
335    #[test]
336    fn months() {
337        let mut months = wrap!(11);
338        for _ in 0..11 {
339            months += 1;
340        }
341        assert_eq!(months.value, 0);
342    }
343
344    #[test]
345    fn custom_min() {
346        let mut mins = wrap!(5, 5, 7);
347        mins += 3;
348        // 5+1 = 6
349        // 6+1 = 7 (wrapped) = 5
350        // 5+1 = 6
351        assert_eq!(mins.value, 6);
352    }
353
354    #[test]
355    fn can_convert() {
356        let mut mins = wrap!(=5);
357        mins += 5 as u16;
358    }
359
360    #[test]
361    fn has_indexing() {
362        let here = wrap!(5);
363        let oh = vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
364        assert_eq!(oh[here], 10);
365    }
366
367    #[test]
368    fn are_equals() {
369        let mut here = wrap!(6);
370        here += 5;
371        println!("{}", here);
372        let there = wrap!(50);
373        assert_eq!(here, there + 5);
374    }
375
376    #[test]
377    fn into_integer() {
378        let here = wrap!(420, 0, 69420);
379        let hmm: WrapNum<u32> = 420.into();
380        let as_u32 = u32::from(here);
381    }
382}