diff --git a/agb/src/number.rs b/agb/src/number.rs index 9590798a..632d5ee2 100644 --- a/agb/src/number.rs +++ b/agb/src/number.rs @@ -1,9 +1,9 @@ use core::{ - fmt::Display, - ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + fmt::{Debug, Display}, + 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(i32); pub fn change_base(num: Num) -> Num { @@ -96,6 +96,25 @@ where } } +impl Rem for Num +where + T: Into>, +{ + type Output = Self; + fn rem(self, modulus: T) -> Self::Output { + Num(self.0 % modulus.into().0) + } +} + +impl RemAssign for Num +where + T: Into>, +{ + fn rem_assign(&mut self, modulus: T) { + self.0 = (*self % modulus).0 + } +} + impl Neg for Num { type Output = Self; fn neg(self) -> Self::Output { @@ -107,12 +126,33 @@ impl Num { pub fn max() -> Self { Num(i32::MAX) } + pub fn min() -> Self { Num(i32::MIN) } 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 { @@ -183,6 +223,71 @@ fn test_change_base(_gba: &mut super::Gba) { 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 Display for Num { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let integral = self.0 >> N; @@ -203,3 +308,9 @@ impl Display for Num { Ok(()) } } + +impl Debug for Num { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Num<{}>({})", N, self) + } +}