use std::ops::{Add, Div, Rem, Sub};
use crate::op_mode::{BringInRange, Wrap};
use super::op_mode;
use crate::*;
use paste::paste;
impl<N> BringInRange<N> for Wrap
where
N: Ord
+ Sub<Output = N>
+ TryFrom<u8>
+ Add<Output = N>
+ Div<Output = N>
+ Rem<Output = N>
+ Copy
{
fn bring_in_range(v: N, min: N, max: N) -> N
{
let mut n = v;
let one: N;
unsafe {
one = 1u8.try_into().unwrap_unchecked();
}
if n > max
{
let interval_size = max - min + one;
n = ((n - min) % interval_size) + min;
}
else if n < min
{
let interval_size = max - min + one;
let modulo = min % interval_size;
n = min + (n + interval_size - modulo) % interval_size;
}
n
}
}
#[cfg(test)]
fn wrap(n: u32, min: u32, max: u32) -> u32
{
let mut n = n;
if n > max
{
let interval_size = max - min + 1;
n = ((n - min) % interval_size) + min;
}
else if n < min
{
let interval_size = max - min + 1;
let times = min / interval_size;
let modulo = min % interval_size;
let range_index = min % interval_size;
eprintln!(
"n: {}, times: {}, modulo:{modulo}, interval_size: {}, range_index: {range_index}",
n, times, interval_size
);
n = min + (n + interval_size - modulo) % interval_size;
}
n
}
macro_rules! gen_wrap_impl {
($i:ty, $bigger_type:ty) => {
paste!{
impl<const MIN: $i, const MAX: $i> std::ops::Add for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
type Output = Self;
fn add(self, rhs: [<Ranged $i:upper>]<MIN,MAX, op_mode::Wrap>) -> Self::Output {
self + rhs.inner()
}
}
impl<const MIN: $i, const MAX: $i> std::ops::Add<$i> for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
type Output = Self;
fn add(self, rhs: $i) -> Self::Output
{
let v1 = self.inner();
let v2 = rhs;
let delta: $i = (MIN - $i::MIN) + ($i::MAX - MAX);
Self::new(
match v1.checked_add(v2)
{
Some(res) => res,
None => {
match v2
{
v2 if v2 > 0 => {
let r1 = v1.wrapping_add(v2);
match r1.checked_add(delta)
{
Some(res) => res,
None => {r1.wrapping_add(delta).checked_add(delta).unwrap()}
}
}
v2 if v2 <= 0 => {
let r1 = v1.wrapping_add(v2);
match r1.checked_sub(delta)
{
Some(res) => res,
None => {r1.wrapping_sub(delta).checked_sub(delta).unwrap()}
}
}
_ => {panic!("Can't get here, we're adding 0 and it should have been checked before")}
}
}
}
)
}
}
impl<const MIN: $i, const MAX: $i> std::ops::AddAssign for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
fn add_assign(&mut self, rhs: [<Ranged $i:upper>]<MIN,MAX, op_mode::Wrap>)
{
*self += rhs.inner();
}
}
impl<const MIN: $i, const MAX: $i> std::ops::AddAssign<$i> for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
fn add_assign(&mut self, rhs: $i)
{
*self = *self + rhs;
}
}
impl<const MIN: $i, const MAX: $i> std::ops::Sub for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
type Output = Self;
fn sub(self, rhs: [<Ranged $i:upper>]<MIN,MAX, op_mode::Wrap>) -> Self::Output {
self - rhs.inner()
}
}
impl<const MIN: $i, const MAX: $i> std::ops::Sub<$i> for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
type Output = Self;
fn sub(self, rhs: $i) -> Self::Output
{
let v1 = self.inner();
let v2 = rhs;
let delta: $i = (MIN - $i::MIN) + ($i::MAX - MAX);
Self::new(
match v1.checked_sub(v2)
{
Some(res) => res,
None => {
match v2
{
v2 if v2 > 0 => {
let r1 = v1.wrapping_sub(v2);
match r1.checked_sub(delta)
{
Some(res) => res,
None => {r1.wrapping_sub(delta).checked_sub(delta).unwrap()}
}
}
v2 if v2 <= 0 => {
let r1 = v1.wrapping_sub(v2);
match r1.checked_add(delta)
{
Some(res) => res,
None => {r1.wrapping_add(delta).checked_add(delta).unwrap()}
}
}
_ => {panic!("Can't get here, we're adding 0 and it should have been checked before")}
}
}
}
)
}
}
impl<const MIN: $i, const MAX: $i> std::ops::SubAssign for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
fn sub_assign(&mut self, rhs: [<Ranged $i:upper>]<MIN,MAX, op_mode::Wrap>)
{
*self -= rhs.inner();
}
}
impl<const MIN: $i, const MAX: $i> std::ops::SubAssign<$i> for [<Ranged $i:upper>]<MIN, MAX, op_mode::Wrap>
{
fn sub_assign(&mut self, rhs: $i)
{
*self = *self - rhs;
}
}
}
};
}
gen_wrap_impl!(u8, u16);
gen_wrap_impl!(i8, i16);
gen_wrap_impl!(u16, u32);
gen_wrap_impl!(i16, i32);
gen_wrap_impl!(u32, u64);
gen_wrap_impl!(i32, i64);
gen_wrap_impl!(u64, u128);
gen_wrap_impl!(i64, i128);
#[cfg(test)]
mod tests
{
use crate::{op_mode::Wrap, RangedI8, RangedU8};
use super::wrap;
fn run_test(input: &[u32], output: &[u32], interval_min: u32, interval_max: u32)
{
for (i, o) in input.iter().zip(output)
{
println!("{} {}", wrap(*i, interval_min, interval_max), *o);
}
for (i, o) in input.iter().zip(output)
{
assert_eq!(wrap(*i, interval_min, interval_max), *o);
}
}
#[test]
fn test_against_stdlib()
{
assert_eq!(wrap(255 + 2, 0, 255), 255u8.wrapping_add(2).into());
assert_eq!(
wrap(255 + 255 + 2, 0, 255),
255u8.wrapping_add(2).wrapping_add(255).into()
);
assert_eq!(wrap(255 + 2, 0, 255), 255u8.wrapping_add(2).into());
assert_eq!(
wrap(255 + 255 + 2, 0, 255),
255u8.wrapping_add(2).wrapping_add(255).into()
);
}
#[test]
fn test_1()
{
assert_eq!(wrap(6, 0, 5), 0);
assert_eq!(wrap(6, 1, 5), 1);
assert_eq!(wrap(15, 0, 5), 15 % 6);
assert_eq!(wrap(15, 0, 5), 3);
}
#[test]
fn test_2()
{
const INPUTS: [u32; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const OUTPUTS: [u32; 16] = [3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3];
run_test(&INPUTS, &OUTPUTS, 2, 4);
}
#[test]
fn test_3()
{
const INTERVAL: [u32; 2] = [6, 8];
const INPUTS: [u32; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const OUTPUTS: [u32; 16] = [6, 7, 8, 6, 7, 8, 6, 7, 8, 6, 7, 8, 6, 7, 8, 6];
run_test(&INPUTS, &OUTPUTS, INTERVAL[0], INTERVAL[1]);
}
#[test]
fn test_4()
{
const INTERVAL: [u32; 2] = [11, 15];
const INPUTS: [u32; 19] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
];
const OUTPUTS: [u32; 19] = [
15, 11, 12, 13, 14, 15, 11, 12, 13, 14, 15, 11, 12, 13, 14, 15, 11, 12, 13
];
run_test(&INPUTS, &OUTPUTS, INTERVAL[0], INTERVAL[1]);
}
#[test]
fn test_type_overflow()
{
let n1: RangedU8<10, 250, Wrap> = RangedU8::new(245);
let n2: RangedU8<10, 250, Wrap> = RangedU8::new(13);
assert_eq!(n1 + n2, 17);
let n1: RangedI8<-120, 100, Wrap> = RangedI8::new(-118);
let n2: RangedI8<-120, 100, Wrap> = RangedI8::new(-13);
let n3: RangedI8<-120, 100, Wrap> = RangedI8::new(13);
assert_eq!(n1 + n2, 90);
assert_eq!(n1 - n3, 90);
let m = RangedU8::<5, 250, Wrap>::new(5);
assert_eq!(m - 1, 250);
assert_eq!(m - 10, 241);
}
}