mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-24 00:31:34 +11:00
Merge pull request #53 from gwilymk/allow-multiple-integer-sizes-for-fixnum
Allow multiple integer sizes for fixnum
This commit is contained in:
commit
e8bc714d74
|
@ -1,12 +1,83 @@
|
|||
use core::{
|
||||
cmp::{Eq, Ord, PartialEq, PartialOrd},
|
||||
fmt::{Debug, Display},
|
||||
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
|
||||
ops::{
|
||||
Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, Shr,
|
||||
Sub, SubAssign,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Num<const N: usize>(i32);
|
||||
pub trait FixedWidthUnsignedInteger:
|
||||
Sized
|
||||
+ Copy
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Shl<usize, Output = Self>
|
||||
+ Shr<usize, Output = Self>
|
||||
+ Add<Output = Self>
|
||||
+ Sub<Output = Self>
|
||||
+ Not<Output = Self>
|
||||
+ BitAnd<Output = Self>
|
||||
+ Rem<Output = Self>
|
||||
+ Div<Output = Self>
|
||||
+ Mul<Output = Self>
|
||||
+ From<u8>
|
||||
+ Debug
|
||||
+ Display
|
||||
{
|
||||
fn zero() -> Self;
|
||||
fn one() -> Self;
|
||||
fn ten() -> Self;
|
||||
}
|
||||
|
||||
pub fn change_base<const N: usize, const M: usize>(num: Num<N>) -> Num<M> {
|
||||
pub trait FixedWidthSignedInteger: FixedWidthUnsignedInteger + Neg<Output = Self> {
|
||||
fn fixed_abs(self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! fixed_width_unsigned_integer_impl {
|
||||
($T: ty) => {
|
||||
impl FixedWidthUnsignedInteger for $T {
|
||||
fn zero() -> Self {
|
||||
0
|
||||
}
|
||||
fn one() -> Self {
|
||||
1
|
||||
}
|
||||
fn ten() -> Self {
|
||||
10
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! fixed_width_signed_integer_impl {
|
||||
($T: ty) => {
|
||||
impl FixedWidthSignedInteger for $T {
|
||||
fn fixed_abs(self) -> Self {
|
||||
self.abs()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixed_width_unsigned_integer_impl!(i16);
|
||||
fixed_width_unsigned_integer_impl!(u16);
|
||||
fixed_width_unsigned_integer_impl!(i32);
|
||||
fixed_width_unsigned_integer_impl!(u32);
|
||||
|
||||
fixed_width_signed_integer_impl!(i16);
|
||||
fixed_width_signed_integer_impl!(i32);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Num<I: FixedWidthUnsignedInteger, const N: usize>(I);
|
||||
|
||||
pub type Number<const N: usize> = Num<i32, N>;
|
||||
|
||||
pub fn change_base<I: FixedWidthUnsignedInteger, const N: usize, const M: usize>(
|
||||
num: Num<I, N>,
|
||||
) -> Num<I, M> {
|
||||
if N < M {
|
||||
Num(num.0 << (M - N))
|
||||
} else {
|
||||
|
@ -14,15 +85,16 @@ pub fn change_base<const N: usize, const M: usize>(num: Num<N>) -> Num<M> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<i32> for Num<N> {
|
||||
fn from(value: i32) -> Self {
|
||||
impl<I: FixedWidthUnsignedInteger, const N: usize> From<I> for Num<I, N> {
|
||||
fn from(value: I) -> Self {
|
||||
Num(value << N)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Add<T> for Num<N>
|
||||
impl<I, T, const N: usize> Add<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
type Output = Self;
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
|
@ -30,18 +102,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> AddAssign<T> for Num<N>
|
||||
impl<I, T, const N: usize> AddAssign<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
fn add_assign(&mut self, rhs: T) {
|
||||
self.0 = (*self + rhs.into()).0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Sub<T> for Num<N>
|
||||
impl<I, T, const N: usize> Sub<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: T) -> Self::Output {
|
||||
|
@ -49,18 +123,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> SubAssign<T> for Num<N>
|
||||
impl<I, T, const N: usize> SubAssign<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
fn sub_assign(&mut self, rhs: T) {
|
||||
self.0 = (*self - rhs.into()).0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Mul<T> for Num<N>
|
||||
impl<I, T, const N: usize> Mul<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
|
@ -68,18 +144,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> MulAssign<T> for Num<N>
|
||||
impl<I, T, const N: usize> MulAssign<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
self.0 = (*self * rhs.into()).0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Div<T> for Num<N>
|
||||
impl<I, T, const N: usize> Div<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
type Output = Self;
|
||||
fn div(self, rhs: T) -> Self::Output {
|
||||
|
@ -87,18 +165,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> DivAssign<T> for Num<N>
|
||||
impl<I, T, const N: usize> DivAssign<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
fn div_assign(&mut self, rhs: T) {
|
||||
self.0 = (*self / rhs.into()).0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Rem<T> for Num<N>
|
||||
impl<I, T, const N: usize> Rem<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
type Output = Self;
|
||||
fn rem(self, modulus: T) -> Self::Output {
|
||||
|
@ -106,45 +186,38 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> RemAssign<T> for Num<N>
|
||||
impl<I, T, const N: usize> RemAssign<T> for Num<I, N>
|
||||
where
|
||||
T: Into<Num<N>>,
|
||||
I: FixedWidthUnsignedInteger,
|
||||
T: Into<Num<I, N>>,
|
||||
{
|
||||
fn rem_assign(&mut self, modulus: T) {
|
||||
self.0 = (*self % modulus).0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Neg for Num<N> {
|
||||
impl<I: FixedWidthSignedInteger, const N: usize> Neg for Num<I, N> {
|
||||
type Output = Self;
|
||||
fn neg(self) -> Self::Output {
|
||||
Num(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Num<N> {
|
||||
pub const fn max() -> Self {
|
||||
Num(i32::MAX)
|
||||
}
|
||||
|
||||
pub const fn min() -> Self {
|
||||
Num(i32::MIN)
|
||||
}
|
||||
|
||||
pub const fn from_raw(n: i32) -> Self {
|
||||
impl<I: FixedWidthUnsignedInteger, const N: usize> Num<I, N> {
|
||||
pub fn from_raw(n: I) -> Self {
|
||||
Num(n)
|
||||
}
|
||||
|
||||
pub const fn to_raw(&self) -> i32 {
|
||||
pub fn to_raw(self) -> I {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn trunc(&self) -> i32 {
|
||||
let fractional_part = self.0 & ((1 << N) - 1);
|
||||
pub fn trunc(&self) -> I {
|
||||
let fractional_part = self.0 & ((I::one() << N) - I::one());
|
||||
let self_as_int = self.0 >> N;
|
||||
|
||||
if self_as_int < 0 && fractional_part != 0 {
|
||||
self_as_int + 1
|
||||
if self_as_int < I::zero() && fractional_part != I::zero() {
|
||||
self_as_int + I::one()
|
||||
} else {
|
||||
self_as_int
|
||||
}
|
||||
|
@ -152,8 +225,8 @@ impl<const N: usize> Num<N> {
|
|||
|
||||
pub fn rem_euclid(&self, rhs: Self) -> Self {
|
||||
let r = *self % rhs;
|
||||
if r < 0.into() {
|
||||
if rhs < 0.into() {
|
||||
if r < I::zero().into() {
|
||||
if rhs < I::zero().into() {
|
||||
r - rhs
|
||||
} else {
|
||||
r + rhs
|
||||
|
@ -163,48 +236,57 @@ impl<const N: usize> Num<N> {
|
|||
}
|
||||
}
|
||||
|
||||
pub const fn floor(&self) -> i32 {
|
||||
pub fn floor(&self) -> I {
|
||||
self.0 >> N
|
||||
}
|
||||
|
||||
pub const fn abs(self) -> Self {
|
||||
Num(self.0.abs())
|
||||
pub fn new(integral: I) -> Self {
|
||||
Self(integral << N)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: FixedWidthSignedInteger, const N: usize> Num<I, N> {
|
||||
pub fn abs(self) -> Self {
|
||||
Num(self.0.fixed_abs())
|
||||
}
|
||||
|
||||
/// domain of [0, 1].
|
||||
/// see https://github.com/tarcieri/micromath/blob/24584465b48ff4e87cffb709c7848664db896b4f/src/float/cos.rs#L226
|
||||
pub fn cos(self) -> Self {
|
||||
let one: Self = 1.into();
|
||||
let one: Self = I::one().into();
|
||||
let mut x = self;
|
||||
x -= one / 4 + (x + one / 4).floor();
|
||||
x *= (x.abs() - one / 2) * 16;
|
||||
x += x * (x.abs() - 1) * 9 / 40;
|
||||
let four: I = 4.into();
|
||||
let two: I = 2.into();
|
||||
let sixteen: I = 16.into();
|
||||
let nine: I = 9.into();
|
||||
let forty: I = 40.into();
|
||||
|
||||
x -= one / four + (x + one / four).floor();
|
||||
x *= (x.abs() - one / two) * sixteen;
|
||||
x += x * (x.abs() - one) * (nine / forty);
|
||||
x
|
||||
}
|
||||
|
||||
pub fn sin(self) -> Self {
|
||||
let one: Self = 1.into();
|
||||
(self - one / 4).cos()
|
||||
}
|
||||
|
||||
pub const fn new(integral: i32) -> Self {
|
||||
Self(integral << N)
|
||||
let one: Self = I::one().into();
|
||||
let four: I = 4.into();
|
||||
(self - one / four).cos()
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn test_numbers(_gba: &mut super::Gba) {
|
||||
// test addition
|
||||
let n: Num<8> = 1.into();
|
||||
let n: Num<i32, 8> = 1.into();
|
||||
assert_eq!(n + 2, 3.into(), "testing that 1 + 2 == 3");
|
||||
|
||||
// test multiplication
|
||||
let n: Num<8> = 5.into();
|
||||
let n: Num<i32, 8> = 5.into();
|
||||
assert_eq!(n * 3, 15.into(), "testing that 5 * 3 == 15");
|
||||
|
||||
// test division
|
||||
let n: Num<8> = 30.into();
|
||||
let p: Num<8> = 3.into();
|
||||
let n: Num<i32, 8> = 30.into();
|
||||
let p: Num<i32, 8> = 3.into();
|
||||
assert_eq!(n / 20, p / 2, "testing that 30 / 20 == 3 / 2");
|
||||
|
||||
assert_ne!(n, p, "testing that 30 != 3");
|
||||
|
@ -212,20 +294,20 @@ fn test_numbers(_gba: &mut super::Gba) {
|
|||
|
||||
#[test_case]
|
||||
fn test_division_by_one(_gba: &mut super::Gba) {
|
||||
let one: Num<8> = 1.into();
|
||||
let one: Num<i32, 8> = 1.into();
|
||||
|
||||
for i in -40..40 {
|
||||
let n: Num<8> = i.into();
|
||||
let n: Num<i32, 8> = i.into();
|
||||
assert_eq!(n / one, n);
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn test_division_and_multiplication_by_16(_gba: &mut super::Gba) {
|
||||
let sixteen: Num<8> = 16.into();
|
||||
let sixteen: Num<i32, 8> = 16.into();
|
||||
|
||||
for i in -40..40 {
|
||||
let n: Num<8> = i.into();
|
||||
let n: Num<i32, 8> = i.into();
|
||||
let m = n / sixteen;
|
||||
|
||||
assert_eq!(m * sixteen, n);
|
||||
|
@ -234,12 +316,12 @@ fn test_division_and_multiplication_by_16(_gba: &mut super::Gba) {
|
|||
|
||||
#[test_case]
|
||||
fn test_division_by_2_and_15(_gba: &mut super::Gba) {
|
||||
let two: Num<8> = 2.into();
|
||||
let fifteen: Num<8> = 15.into();
|
||||
let thirty: Num<8> = 30.into();
|
||||
let two: Num<i32, 8> = 2.into();
|
||||
let fifteen: Num<i32, 8> = 15.into();
|
||||
let thirty: Num<i32, 8> = 30.into();
|
||||
|
||||
for i in -128..128 {
|
||||
let n: Num<8> = i.into();
|
||||
let n: Num<i32, 8> = i.into();
|
||||
|
||||
assert_eq!(n / two / fifteen, n / thirty);
|
||||
assert_eq!(n / fifteen / two, n / thirty);
|
||||
|
@ -248,8 +330,8 @@ fn test_division_by_2_and_15(_gba: &mut super::Gba) {
|
|||
|
||||
#[test_case]
|
||||
fn test_change_base(_gba: &mut super::Gba) {
|
||||
let two: Num<9> = 2.into();
|
||||
let three: Num<4> = 3.into();
|
||||
let two: Num<i32, 9> = 2.into();
|
||||
let three: Num<i32, 4> = 3.into();
|
||||
|
||||
assert_eq!(two + change_base(three), 5.into());
|
||||
assert_eq!(three + change_base(two), 5.into());
|
||||
|
@ -264,7 +346,7 @@ fn test_rem_returns_sensible_values_for_integers(_gba: &mut super::Gba) {
|
|||
}
|
||||
|
||||
let i_rem_j_normally = i % j;
|
||||
let i_fixnum: Num<8> = i.into();
|
||||
let i_fixnum: Num<i32, 8> = i.into();
|
||||
|
||||
assert_eq!(i_fixnum % j, i_rem_j_normally.into());
|
||||
}
|
||||
|
@ -273,7 +355,7 @@ fn test_rem_returns_sensible_values_for_integers(_gba: &mut super::Gba) {
|
|||
|
||||
#[test_case]
|
||||
fn test_rem_returns_sensible_values_for_non_integers(_gba: &mut super::Gba) {
|
||||
let one: Num<8> = 1.into();
|
||||
let one: Num<i32, 8> = 1.into();
|
||||
let third = one / 3;
|
||||
|
||||
for i in -50..50 {
|
||||
|
@ -283,10 +365,10 @@ fn test_rem_returns_sensible_values_for_non_integers(_gba: &mut super::Gba) {
|
|||
}
|
||||
|
||||
// full calculation in the normal way
|
||||
let x: Num<8> = third + i;
|
||||
let y: Num<8> = j.into();
|
||||
let x: Num<i32, 8> = third + i;
|
||||
let y: Num<i32, 8> = j.into();
|
||||
|
||||
let truncated_division: Num<8> = (x / y).trunc().into();
|
||||
let truncated_division: Num<i32, 8> = (x / y).trunc().into();
|
||||
|
||||
let remainder = x - truncated_division * y;
|
||||
|
||||
|
@ -297,7 +379,7 @@ fn test_rem_returns_sensible_values_for_non_integers(_gba: &mut super::Gba) {
|
|||
|
||||
#[test_case]
|
||||
fn test_rem_euclid_is_always_positive_and_sensible(_gba: &mut super::Gba) {
|
||||
let one: Num<8> = 1.into();
|
||||
let one: Num<i32, 8> = 1.into();
|
||||
let third = one / 3;
|
||||
|
||||
for i in -50..50 {
|
||||
|
@ -306,13 +388,8 @@ fn test_rem_euclid_is_always_positive_and_sensible(_gba: &mut super::Gba) {
|
|||
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).trunc().into();
|
||||
|
||||
let remainder = x - truncated_division * y;
|
||||
let x: Num<i32, 8> = third + i;
|
||||
let y: Num<i32, 8> = j.into();
|
||||
|
||||
let rem_euclid = x.rem_euclid(y);
|
||||
assert!(rem_euclid > 0.into());
|
||||
|
@ -320,29 +397,32 @@ fn test_rem_euclid_is_always_positive_and_sensible(_gba: &mut super::Gba) {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Display for Num<N> {
|
||||
impl<I: FixedWidthUnsignedInteger, const N: usize> Display for Num<I, N> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let integral = self.0 >> N;
|
||||
let mask: u32 = (1 << N) - 1;
|
||||
let mask: I = (I::one() << N) - I::one();
|
||||
|
||||
write!(f, "{}", integral)?;
|
||||
|
||||
let mut fractional = self.0 as u32 & mask;
|
||||
if fractional & mask != 0 {
|
||||
let mut fractional = self.0 & mask;
|
||||
if fractional & mask != I::zero() {
|
||||
write!(f, ".")?;
|
||||
}
|
||||
while fractional & mask != 0 {
|
||||
fractional *= 10;
|
||||
|
||||
while fractional & mask != I::zero() {
|
||||
fractional = fractional * I::ten();
|
||||
write!(f, "{}", (fractional & !mask) >> N)?;
|
||||
fractional &= mask;
|
||||
fractional = fractional & mask;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Debug for Num<N> {
|
||||
impl<I: FixedWidthUnsignedInteger, const N: usize> Debug for Num<I, N> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "Num<{}>({})", N, self)
|
||||
use core::any::type_name;
|
||||
|
||||
write!(f, "Num<{}, {}>({})", type_name::<I>(), N, self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,11 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn affine_matrix(x_scale: Num<8>, y_scale: Num<8>, rotation: u8) -> AffineMatrixAttributes {
|
||||
pub fn affine_matrix(
|
||||
x_scale: Num<i16, 8>,
|
||||
y_scale: Num<i16, 8>,
|
||||
rotation: u8,
|
||||
) -> AffineMatrixAttributes {
|
||||
let mut result = AffineMatrixAttributes {
|
||||
p_a: 0,
|
||||
p_b: 0,
|
||||
|
@ -125,8 +129,8 @@ pub fn affine_matrix(x_scale: Num<8>, y_scale: Num<8>, rotation: u8) -> AffineMa
|
|||
let rotation_for_input = (rotation as u16) << 8;
|
||||
|
||||
let input = Input {
|
||||
y_scale: x_scale.to_raw() as i16,
|
||||
x_scale: y_scale.to_raw() as i16,
|
||||
y_scale: x_scale.to_raw(),
|
||||
x_scale: y_scale.to_raw(),
|
||||
rotation: rotation_for_input,
|
||||
};
|
||||
|
||||
|
@ -145,9 +149,9 @@ pub fn affine_matrix(x_scale: Num<8>, y_scale: Num<8>, rotation: u8) -> AffineMa
|
|||
#[test_case]
|
||||
fn affine(_gba: &mut crate::Gba) {
|
||||
// expect identity matrix
|
||||
let one: Num<8> = 1.into();
|
||||
let one: Num<i16, 8> = 1.into();
|
||||
|
||||
let aff = affine_matrix(one, one, 0);
|
||||
assert_eq!(aff.p_a, one.to_raw() as i16);
|
||||
assert_eq!(aff.p_d, one.to_raw() as i16);
|
||||
assert_eq!(aff.p_a, one.to_raw());
|
||||
assert_eq!(aff.p_d, one.to_raw());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue