Merge pull request #43 from gwilymk/add-rem-and-rem-euclid

Add rem and rem euclid implementations for fixnum
This commit is contained in:
Corwin 2021-06-05 17:30:34 +01:00 committed by GitHub
commit e2925eb917
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,9 +1,9 @@
use core::{ use core::{
fmt::Display, fmt::{Debug, Display},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
}; };
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Num<const N: usize>(i32); pub struct Num<const N: usize>(i32);
pub fn change_base<const N: usize, const M: usize>(num: Num<N>) -> Num<M> { pub fn change_base<const N: usize, const M: usize>(num: Num<N>) -> Num<M> {
@ -96,6 +96,25 @@ where
} }
} }
impl<T, const N: usize> Rem<T> for Num<N>
where
T: Into<Num<N>>,
{
type Output = Self;
fn rem(self, modulus: T) -> Self::Output {
Num(self.0 % modulus.into().0)
}
}
impl<T, const N: usize> RemAssign<T> for Num<N>
where
T: Into<Num<N>>,
{
fn rem_assign(&mut self, modulus: T) {
self.0 = (*self % modulus).0
}
}
impl<const N: usize> Neg for Num<N> { impl<const N: usize> Neg for Num<N> {
type Output = Self; type Output = Self;
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
@ -107,12 +126,33 @@ impl<const N: usize> Num<N> {
pub fn max() -> Self { pub fn max() -> Self {
Num(i32::MAX) Num(i32::MAX)
} }
pub fn min() -> Self { pub fn min() -> Self {
Num(i32::MIN) Num(i32::MIN)
} }
pub fn int(&self) -> i32 { pub fn int(&self) -> i32 {
self.0 >> N let fractional_part = self.0 & ((1 << N) - 1);
let self_as_int = self.0 >> N;
if self_as_int < 0 && fractional_part != 0 {
self_as_int + 1
} else {
self_as_int
}
}
pub fn rem_euclid(&self, rhs: Self) -> Self {
let r = *self % rhs;
if r < 0.into() {
if rhs < 0.into() {
r - rhs
} else {
r + rhs
}
} else {
r
}
} }
pub fn new(integral: i32) -> Self { pub fn new(integral: i32) -> Self {
@ -183,6 +223,71 @@ fn test_change_base(_gba: &mut super::Gba) {
assert_eq!(three + change_base(two), 5.into()); assert_eq!(three + change_base(two), 5.into());
} }
#[test_case]
fn test_rem_returns_sensible_values_for_integers(_gba: &mut super::Gba) {
for i in -50..50 {
for j in -50..50 {
if j == 0 {
continue;
}
let i_rem_j_normally = i % j;
let i_fixnum: Num<8> = i.into();
assert_eq!(i_fixnum % j, i_rem_j_normally.into());
}
}
}
#[test_case]
fn test_rem_returns_sensible_values_for_non_integers(_gba: &mut super::Gba) {
let one: Num<8> = 1.into();
let third = one / 3;
for i in -50..50 {
for j in -50..50 {
if j == 0 {
continue;
}
// full calculation in the normal way
let x: Num<8> = third + i;
let y: Num<8> = j.into();
let truncated_division: Num<8> = (x / y).int().into();
let remainder = x - truncated_division * y;
assert_eq!(x % y, remainder);
}
}
}
#[test_case]
fn test_rem_euclid_is_always_positive_and_sensible(_gba: &mut super::Gba) {
let one: Num<8> = 1.into();
let third = one / 3;
for i in -50..50 {
for j in -50..50 {
if j == 0 {
continue;
}
// full calculation in the normal way
let x: Num<8> = third + i;
let y: Num<8> = j.into();
let truncated_division: Num<8> = (x / y).int().into();
let remainder = x - truncated_division * y;
let rem_euclid = x.rem_euclid(y);
assert!(rem_euclid > 0.into());
}
}
}
impl<const N: usize> Display for Num<N> { impl<const N: usize> Display for Num<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let integral = self.0 >> N; let integral = self.0 >> N;
@ -203,3 +308,9 @@ impl<const N: usize> Display for Num<N> {
Ok(()) Ok(())
} }
} }
impl<const N: usize> Debug for Num<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Num<{}>({})", N, self)
}
}