diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 499c0d214..c050505db 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -3,15 +3,15 @@ //! Fixed point number implementation for representing non integers efficiently. use core::{ - cmp::{Eq, Ord, PartialEq, PartialOrd}, + cmp::{Ord, PartialOrd}, fmt::{Debug, Display}, mem::size_of, - ops::{ - Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, Shr, - Sub, SubAssign, - }, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, +}; +use num_traits::{ + ops::overflowing::{OverflowingAdd, OverflowingSub}, + One, PrimInt, Signed, WrappingAdd, WrappingSub, Zero, }; -use num_traits::Signed; #[doc(hidden)] /// Used internally by the [num!] macro which should be used instead. @@ -35,43 +35,30 @@ macro_rules! num { /// fixed point number. pub trait Number: Copy + PartialOrd + Ord + num_traits::Num {} -impl Number for Num {} -impl Number for I {} - -/// A trait for integers that don't implement unary negation -pub trait FixedWidthUnsignedInteger: - Copy - + PartialOrd - + Ord - + Shl - + Shr - + BitAnd - + From - + Debug - + Display - + num_traits::Num - + Not +impl Number for Num {} +impl Number for I {} + +/// A trait for integers with fixed width +pub trait FixedWidthInteger: + Number + PrimInt + OverflowingAdd + OverflowingSub + WrappingAdd + WrappingSub + Display { - /// Returns the representation of ten - fn ten() -> Self; /// Converts an i32 to it's own representation, panics on failure fn from_as_i32(v: i32) -> Self; /// Returns (a * b) >> N - fn upcast_multiply(a: Self, b: Self, n: usize) -> Self; + fn upcast_multiply(a: Self, b: Self) -> Self; + /// Returns Some((a * b) >> N) if the multiplication didn't overflowed + fn upcast_multiply_checked(a: Self, b: Self) -> Option; + /// Returns ((a * b) >> N, flag), where flag is true if the operation overflowed + fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool); + /// Returns (a * b) >> N, saturating at the numeric bounds instead of overflowing + fn upcast_multiply_saturating(a: Self, b: Self) -> Self; + /// Returns (a * b) >> N, but doesn't panic in case of overflow in debug mode + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self; } -/// Trait for an integer that includes negation -pub trait FixedWidthSignedInteger: FixedWidthUnsignedInteger + num_traits::sign::Signed {} - -impl FixedWidthSignedInteger for I {} - -macro_rules! fixed_width_unsigned_integer_impl { +macro_rules! fixed_width_integer_impl { ($T: ty, $Upcast: ident) => { - impl FixedWidthUnsignedInteger for $T { - #[inline(always)] - fn ten() -> Self { - 10 - } + impl FixedWidthInteger for $T { #[inline(always)] fn from_as_i32(v: i32) -> Self { v as $T @@ -82,50 +69,320 @@ macro_rules! fixed_width_unsigned_integer_impl { }; } -macro_rules! upcast_multiply_impl { - ($T: ty, optimised_64_bit) => { - #[inline(always)] - fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { - use num_traits::One; +#[cfg(target_arch = "arm")] +#[cfg_attr(target_feature = "thumb-mode", instruction_set(arm::a32))] +#[cfg_attr(not(target_feature = "thumb-mode"), inline(always))] +fn upcast_multiply_wide_unsigned(a: u32, b: u32) -> (u32, u32) { + use core::arch::asm; + let low: u32; + let high: u32; + unsafe { + asm!( + "UMULL {low}, {high}, {a}, {b}", + a = in(reg) a, + b = in(reg) b, + low = lateout(reg) low, + high = lateout(reg) high, + ); + } + (low, high) +} - let mask = (Self::one() << n).wrapping_sub(1); +#[cfg(target_arch = "arm")] +#[cfg_attr(target_feature = "thumb-mode", instruction_set(arm::a32))] +#[cfg_attr(not(target_feature = "thumb-mode"), inline(always))] +fn upcast_multiply_wide_signed(a: i32, b: i32) -> (u32, i32) { + use core::arch::asm; + let low: u32; + let high: i32; + unsafe { + asm!( + "SMULL {low}, {high}, {a}, {b}", + a = in(reg) a, + b = in(reg) b, + low = lateout(reg) low, + high = lateout(reg) high, + ); + } + (low, high) +} + +macro_rules! upcast_multiply_fast_impl { + ($FnName: ident, i32) => { + upcast_multiply_fast_impl!($FnName, i32, 1); + }; + ($FnName: ident, u32) => { + upcast_multiply_fast_impl!($FnName, u32, 0); + }; + ($FnName: ident, $T: ty, $OverflowTypeExpr: expr) => { + #[cfg(target_arch = "arm")] + #[inline(always)] + fn $FnName(a: $T, b: $T) -> ($T, bool) { + let mask = (1 << N).wrapping_sub(&1); - let a_floor = a >> n; + let a_floor = a >> N; let a_frac = a & mask; - let b_floor = b >> n; + let b_floor = b >> N; let b_frac = b & mask; - (a_floor.wrapping_mul(b_floor) << n) - .wrapping_add( - a_floor - .wrapping_mul(b_frac) - .wrapping_add(b_floor.wrapping_mul(a_frac)), - ) - .wrapping_add(((a_frac as u32).wrapping_mul(b_frac as u32) >> n) as $T) + let (x, is_overflow1) = a_floor.overflowing_mul(b_floor); + // unsigned -> overflow if high != 0 + // signed -> overflow if high != (if res >= 0 {0} else {-1}) + let is_overflow2 = + (x as $T >> (32 - N)) != ($OverflowTypeExpr * ((x << N) as $T >> 31)); + let x = x << N; + let (y, is_overflow3) = a_floor + .wrapping_mul(b_frac) + .overflowing_add(b_floor.wrapping_mul(a_frac)); + // cannot overflow since a_fract and b_fract are at most 16 bits + let z = ((a_frac as u32).wrapping_mul(b_frac as u32) >> N) as $T; + let (res, is_overflow4) = x.overflowing_add(y); + let (res, is_overflow5) = res.overflowing_add(z); + + ( + res, + is_overflow1 || is_overflow2 || is_overflow3 || is_overflow4 || is_overflow5, + ) + } + }; +} + +upcast_multiply_fast_impl!(upcast_multiply_fast_signed, i32); +upcast_multiply_fast_impl!(upcast_multiply_fast_unsigned, u32); + +macro_rules! upcast_multiply_impl { + ($T: ty, optimised_64_bit_signed) => { + #[cfg(not(target_arch = "arm"))] + upcast_multiply_impl!($T, i64); + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply(a: Self, b: Self) -> Self { + let (res, is_overflow) = if N <= 16 { + upcast_multiply_fast_signed::(a, b) + } else { + let (low, high) = upcast_multiply_wide_signed(a, b); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); + (res, is_overflow) + }; + if cfg!(debug_assertions) && is_overflow { + panic!("attempt to multiply with overflow"); + } + res + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_checked(a: Self, b: Self) -> Option { + let (res, is_overflow) = if N <= 16 { + upcast_multiply_fast_signed::(a, b) + } else { + let (low, high) = upcast_multiply_wide_signed(a, b); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); + (res, is_overflow) + }; + if is_overflow { + None + } else { + Some(res) + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { + if N <= 16 { + upcast_multiply_fast_signed::(a, b) + } else { + let (low, high) = upcast_multiply_wide_signed(a, b); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); + (res, is_overflow) + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_saturating(a: Self, b: Self) -> Self { + let (res, is_overflow) = if N <= 16 { + upcast_multiply_fast_signed::(a, b) + } else { + let (low, high) = upcast_multiply_wide_signed(a, b); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); + (res, is_overflow) + }; + if is_overflow { + if (a < 0) ^ (b < 0) { + <$T>::MIN + } else { + <$T>::MAX + } + } else { + res + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { + if N <= 16 { + upcast_multiply_fast_signed::(a, b).0 + } else { + let (low, high) = upcast_multiply_wide_signed(a, b); + ((low >> N) | (high << (32 - N)) as u32) as $T + } + } + }; + ($T: ty, optimised_64_bit_unsigned) => { + #[cfg(not(target_arch = "arm"))] + upcast_multiply_impl!($T, u64); + + #[cfg(target_arch = "arm")] + fn upcast_multiply(a: Self, b: Self) -> Self { + let (res, is_overflow) = if N <= 16 { + upcast_multiply_fast_unsigned::(a, b) + } else { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let res = ((low >> N) | (high << (32 - N))) as $T; + let is_overflow = (high >> N) != 0; + (res, is_overflow) + }; + if cfg!(debug_assertions) && is_overflow { + panic!("attempt to multiply with overflow"); + } + res + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_checked(a: Self, b: Self) -> Option { + let (res, is_overflow) = if N <= 16 { + upcast_multiply_fast_unsigned::(a, b) + } else { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let res = ((low >> N) | (high << (32 - N))) as $T; + let is_overflow = (high >> N) != 0; + (res, is_overflow) + }; + if is_overflow { + None + } else { + Some(res) + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { + if N <= 16 { + upcast_multiply_fast_unsigned::(a, b) + } else { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let res = ((low >> N) | (high << (32 - N))) as $T; + let is_overflow = (high >> N) != 0; + (res, is_overflow) + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_saturating(a: Self, b: Self) -> Self { + let (res, is_overflow) = if N <= 16 { + upcast_multiply_fast_unsigned::(a, b) + } else { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let res = ((low >> N) | (high << (32 - N))) as $T; + let is_overflow = (high >> N) != 0; + (res, is_overflow) + }; + if is_overflow { + <$T>::MAX + } else { + res + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { + if N <= 16 { + upcast_multiply_fast_unsigned::(a, b).0 + } else { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + ((low >> N) | (high << (32 - N))) as $T + } } }; ($T: ty, $Upcast: ty) => { #[inline(always)] - fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { - (((a as $Upcast) * (b as $Upcast)) >> n) as $T + fn upcast_multiply(a: Self, b: Self) -> Self { + let res = ((a as $Upcast) * (b as $Upcast)) >> N; + let is_overflow = res > <$T>::MAX as $Upcast || res < <$T>::MIN as $Upcast; + if cfg!(debug_assertions) && is_overflow { + panic!("attempt to multiply with overflow"); + } + res as $T + } + + #[inline(always)] + fn upcast_multiply_checked(a: Self, b: Self) -> Option { + let res = ((a as $Upcast) * (b as $Upcast)) >> N; + let is_overflow = res > <$T>::MAX as $Upcast || res < <$T>::MIN as $Upcast; + if is_overflow { + None + } else { + Some(res as $T) + } + } + + #[inline(always)] + fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { + let res = ((a as $Upcast) * (b as $Upcast)) >> N; + let is_overflow = res > <$T>::MAX as $Upcast || res < <$T>::MIN as $Upcast; + (res as $T, is_overflow) + } + + #[inline(always)] + fn upcast_multiply_saturating(a: Self, b: Self) -> Self { + let res = ((a as $Upcast) * (b as $Upcast)) >> N; + let is_overflow = res > <$T>::MAX as $Upcast || res < <$T>::MIN as $Upcast; + if is_overflow { + #[allow(unused_comparisons)] + if (a < 0) ^ (b < 0) { + <$T>::MIN + } else { + <$T>::MAX + } + } else { + res as $T + } + } + + #[inline(always)] + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { + ((a as $Upcast) * (b as $Upcast) >> N) as $T } }; } -fixed_width_unsigned_integer_impl!(u8, u32); -fixed_width_unsigned_integer_impl!(i16, i32); -fixed_width_unsigned_integer_impl!(u16, u32); +fixed_width_integer_impl!(i8, i16); +fixed_width_integer_impl!(u8, u16); +fixed_width_integer_impl!(i16, i32); +fixed_width_integer_impl!(u16, u32); -fixed_width_unsigned_integer_impl!(i32, optimised_64_bit); -fixed_width_unsigned_integer_impl!(u32, optimised_64_bit); +fixed_width_integer_impl!(i32, optimised_64_bit_signed); +fixed_width_integer_impl!(u32, optimised_64_bit_unsigned); /// A fixed point number represented using `I` with `N` bits of fractional precision #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct Num(I); +pub struct Num(I); -impl num_traits::Zero for Num { +impl Zero for Num { fn zero() -> Self { Self::new(I::zero()) } @@ -135,13 +392,13 @@ impl num_traits::Zero for Num num_traits::One for Num { +impl One for Num { fn one() -> Self { Self::new(I::one()) } } -impl num_traits::Num for Num { +impl num_traits::Num for Num { type FromStrRadixErr = ::FromStrRadixErr; fn from_str_radix(str: &str, radix: u32) -> Result { @@ -151,10 +408,15 @@ impl num_traits: let v: f64 = f64::from_str_radix(str, radix)?; - let integer = v.trunc(); - let fractional = v.fract() * (1u64 << 30) as f64; + let integer = v.trunc().abs(); + let fractional = v.fract().abs() * (1u64 << 32) as f64; + let sign = v.signum(); - Ok(Self::new_from_parts((integer as i32, fractional as i32))) + Ok(Self::new_from_parts(( + sign as i8, + integer as u32, + fractional as u32, + ))) } } @@ -162,7 +424,7 @@ impl num_traits: /// internal representation for maximum efficiency pub type FixedNum = Num; -impl From for Num { +impl From for Num { fn from(value: I) -> Self { Num(value << N) } @@ -170,7 +432,7 @@ impl From for Num { impl Default for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { fn default() -> Self { Num(I::zero()) @@ -179,7 +441,7 @@ where impl Add for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { type Output = Self; @@ -190,7 +452,7 @@ where impl AddAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { fn add_assign(&mut self, rhs: T) { @@ -200,7 +462,7 @@ where impl Sub for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { type Output = Self; @@ -211,7 +473,7 @@ where impl SubAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { fn sub_assign(&mut self, rhs: T) { @@ -221,17 +483,17 @@ where impl Mul> for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn mul(self, rhs: Num) -> Self::Output { - Num(I::upcast_multiply(self.0, rhs.0, N)) + Num(I::upcast_multiply::(self.0, rhs.0)) } } impl Mul for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn mul(self, rhs: I) -> Self::Output { @@ -241,7 +503,7 @@ where impl MulAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, Num: Mul>, { fn mul_assign(&mut self, rhs: T) { @@ -251,7 +513,7 @@ where impl Div> for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn div(self, rhs: Num) -> Self::Output { @@ -261,7 +523,7 @@ where impl Div for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn div(self, rhs: I) -> Self::Output { @@ -271,7 +533,7 @@ where impl DivAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, Num: Div>, { fn div_assign(&mut self, rhs: T) { @@ -281,7 +543,7 @@ where impl Rem for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { type Output = Self; @@ -292,7 +554,7 @@ where impl RemAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { fn rem_assign(&mut self, modulus: T) { @@ -300,16 +562,16 @@ where } } -impl Neg for Num { +impl Neg for Num { type Output = Self; fn neg(self) -> Self::Output { Num(-self.0) } } -impl Num { +impl Num { /// Performs the conversion between two integer types and between two different fractional precisions - pub fn change_base, const M: usize>(self) -> Num { + pub fn change_base, const M: usize>(self) -> Num { let n: J = self.0.into(); if N < M { Num(n << (M - N)) @@ -330,7 +592,7 @@ impl Num { /// let b: Option> = a.try_change_base(); /// assert_eq!(b, None); /// ``` - pub fn try_change_base, const M: usize>( + pub fn try_change_base, const M: usize>( self, ) -> Option> { if size_of::() > size_of::() { @@ -368,14 +630,14 @@ impl Num { /// because you cannot currently do floating point operations in const contexts, so /// you should use the `num!` macro from agb-macros if you want a const from_f32/f64 pub fn from_f32(input: f32) -> Self { - Self::from_raw(I::from_as_i32((input * (1 << N) as f32) as i32)) + Self::from_raw(I::from_as_i32((input * (1_usize << N) as f32) as i32)) } /// Lossily transforms an f64 into a fixed point representation. This is not const /// because you cannot currently do floating point operations in const contexts, so /// you should use the `num!` macro from agb-macros if you want a const from_f32/f64 pub fn from_f64(input: f64) -> Self { - Self::from_raw(I::from_as_i32((input * (1 << N) as f64) as i32)) + Self::from_raw(I::from_as_i32((input * (1_usize << N) as f64) as i32)) } /// Truncates the fixed point number returning the integral part @@ -451,8 +713,9 @@ impl Num { #[doc(hidden)] #[inline(always)] /// Called by the [num!] macro in order to create a fixed point number - pub fn new_from_parts(num: (i32, i32)) -> Self { - Self(I::from_as_i32(((num.0) << N) + (num.1 >> (30 - N)))) + pub fn new_from_parts((sign, integer, fraction): (i8, u32, u32)) -> Self { + let repr = sign as i64 * (((integer as i64) << N) | ((fraction as i64) >> (32 - N))); + Self(I::from_as_i32(repr as i32)) } } @@ -490,7 +753,7 @@ impl Num { } } -impl Num { +impl Num { #[must_use] /// Returns the absolute value of a fixed point number /// ``` @@ -545,12 +808,12 @@ impl Num { #[must_use] pub fn sin(self) -> Self { let one: Self = I::one().into(); - let four: I = 4.into(); + let four: I = I::from_as_i32(4); (self - one / four).cos() } } -impl num_traits::sign::Signed for Num { +impl Signed for Num { fn abs(&self) -> Self { Self::abs(*self) } @@ -572,44 +835,122 @@ impl num_traits::sign::Signed for Nu } } -impl Display for Num { +impl Num { + /// Checked integer addition. Computes self + rhs, returning None if overflow occurred + pub fn checked_add(&self, rhs: impl Into>) -> Option { + self.0.checked_add(&rhs.into().0).map(|n| Num(n)) + } + + /// Checked integer division. Computes self / rhs, returning None if rhs == 0 or the division results in overflow + pub fn checked_div(&self, rhs: impl Into>) -> Option { + (self.0 << N).checked_div(&rhs.into().0).map(|n| Num(n)) + } + + /// Checked integer multiplication. Computes self * rhs, returning None if overflow occurred + pub fn checked_mul(&self, rhs: impl Into>) -> Option { + I::upcast_multiply_checked::(self.0, rhs.into().0).map(|n| Num(n)) + } + + /// Checked integer subtraction. Computes self - rhs, returning None if overflow occurred + pub fn checked_sub(&self, rhs: impl Into>) -> Option { + self.0.checked_sub(&rhs.into().0).map(|n| Num(n)) + } + + /// Calculates self + rhs + /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur. If an overflow would have occurred then the wrapped value is returned + pub fn overflowing_add(&self, rhs: impl Into>) -> (Self, bool) { + let (res, flag) = self.0.overflowing_add(&rhs.into().0); + (Num(res), flag) + } + + /// Calculates the multiplication of self and rhs. + /// Returns a tuple of the multiplication along with a boolean indicating whether an arithmetic overflow would occur. If an overflow would have occurred then the wrapped value is returned + pub fn overflowing_mul(&self, rhs: impl Into>) -> (Self, bool) { + let (res, flag) = I::upcast_multiply_overflowing::(self.0, rhs.into().0); + (Num(res), flag) + } + + /// Calculates self - rhs + /// Returns a tuple of the subtraction along with a boolean indicating whether an arithmetic overflow would occur. If an overflow would have occurred then the wrapped value is returned + pub fn overflowing_sub(&self, rhs: impl Into>) -> (Self, bool) { + let (res, flag) = self.0.overflowing_sub(&rhs.into().0); + (Num(res), flag) + } + + /// Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead of overflowing + pub fn saturating_add(&self, rhs: impl Into>) -> Self { + Num(self.0.saturating_add(rhs.into().0)) + } + + /// Saturating integer multiplication. Computes self * rhs, saturating at the numeric bounds instead of overflowing + pub fn saturating_mul(&self, rhs: impl Into>) -> Self { + Num(I::upcast_multiply_saturating::(self.0, rhs.into().0)) + } + + /// Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds instead of overflowing + pub fn saturating_sub(&self, rhs: impl Into>) -> Self { + Num(self.0.saturating_sub(rhs.into().0)) + } + + /// Wrapping (modular) addition. Computes self + rhs, wrapping around at the boundary of the type + pub fn wrapping_add(&self, rhs: impl Into>) -> Self { + Num(self.0.wrapping_add(&rhs.into().0)) + } + + /// Wrapping (modular) multiplication. Computes self * rhs, wrapping around at the boundary of the type + pub fn wrapping_mul(&self, rhs: impl Into>) -> Self { + Num(I::upcast_multiply_wrapping::(self.0, rhs.into().0)) + } + + /// Wrapping (modular) subtraction. Computes self - rhs, wrapping around at the boundary of the type + pub fn wrapping_sub(&self, rhs: impl Into>) -> Self { + Num(self.0.wrapping_sub(&rhs.into().0)) + } +} + +impl Display for Num { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut integral = self.0 >> N; - let mask: I = (I::one() << N) - I::one(); + let repr = self + .0 + .to_i64() + .expect("Num's I can always be converted to i64"); + let mut integral = repr >> N; + let mask = (1_i64 << N) - 1; - let mut fractional = self.0 & mask; + let mut fractional = repr & mask; // Negative fixnums are awkward to print if they have non zero fractional part. // This is because you can think of them as `number + non negative fraction`. // // But if you think of a negative number, you'd like it to be `negative number - non negative fraction` // So we have to add 1 to the integral bit, and take 1 - fractional bit - let sign = if fractional != I::zero() && integral < I::zero() { - integral = integral + I::one(); - fractional = (I::one() << N) - fractional; + let sign = if fractional != 0 && integral < 0 { + integral += 1; + fractional = (1 << N) - fractional; -1 } else { 1 }; + let mut integral = integral as i32; if let Some(precision) = f.precision() { - let precision_multiplier = I::from_as_i32(10_i32.pow(precision as u32)); + let precision_multiplier = 10_u32.pow(precision as u32); - let fractional_as_integer = fractional * precision_multiplier * I::ten(); - let mut fractional_as_integer = fractional_as_integer >> N; + let fractional_as_integer = fractional * precision_multiplier as i64 * 10; + let mut fractional_as_integer = (fractional_as_integer >> N) as u32; - if fractional_as_integer % I::ten() >= I::from_as_i32(5) { - fractional_as_integer = fractional_as_integer + I::ten(); + if fractional_as_integer % 10 >= 5 { + fractional_as_integer += 10; } - let mut fraction_to_write = fractional_as_integer / I::ten(); + let mut fraction_to_write = fractional_as_integer / 10; if fraction_to_write >= precision_multiplier { - integral = integral + I::from_as_i32(sign); - fraction_to_write = fraction_to_write - precision_multiplier; + integral += sign; + fraction_to_write -= precision_multiplier; } - if sign == -1 && integral == I::zero() && fraction_to_write != I::zero() { + if sign == -1 && integral == 0 && fraction_to_write != 0 { write!(f, "-")?; } @@ -619,19 +960,19 @@ impl Display for Num { write!(f, ".{:#0width$}", fraction_to_write, width = precision)?; } } else { - if sign == -1 && integral == I::zero() { + if sign == -1 && integral == 0 { write!(f, "-")?; } write!(f, "{integral}")?; - if fractional != I::zero() { + if fractional != 0 { write!(f, ".")?; } - while fractional & mask != I::zero() { - fractional = fractional * I::ten(); + while fractional & mask != 0 { + fractional *= 10; write!(f, "{}", (fractional & !mask) >> N)?; - fractional = fractional & mask; + fractional &= mask; } } @@ -639,7 +980,7 @@ impl Display for Num { } } -impl Debug for Num { +impl Debug for Num { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { use core::any::type_name; @@ -757,7 +1098,7 @@ impl Vector2D { } } -impl Vector2D> { +impl Vector2D> { #[must_use] /// Truncates the x and y coordinate, see [Num::trunc] /// ``` @@ -790,7 +1131,7 @@ impl Vector2D> { #[must_use] /// Attempts to change the base returning None if the numbers cannot be represented - pub fn try_change_base, const M: usize>( + pub fn try_change_base, const M: usize>( self, ) -> Option>> { Some(Vector2D::new( @@ -871,7 +1212,7 @@ impl Vector2D { } } -impl Vector2D> { +impl Vector2D> { /// Creates a unit vector from an angle, noting that the domain of the angle /// is [0, 1], see [Num::cos] and [Num::sin]. /// ``` @@ -887,7 +1228,7 @@ impl Vector2D> { } } -impl From> for Vector2D> { +impl From> for Vector2D> { fn from(n: Vector2D) -> Self { Vector2D { x: n.x.into(), @@ -1018,7 +1359,7 @@ impl Rect { } } -impl Rect { +impl Rect { /// Iterate over the points in a rectangle in row major order. /// ``` /// # use agb_fixnum::*; @@ -1253,6 +1594,18 @@ mod tests { test_precision!(zero_precision_negative, -0.001, "0", 0); test_precision!(zero_precision_positive, 0.001, "0", 0); + + #[test] + fn test_high_precision() { + let a: Num = num!(-0.625); + assert_eq!(format!("{:.3}", a), "-0.625"); + + let a: Num = num!(0.390625); + assert_eq!(format!("{:.6}", a), "0.390625"); + + let a: Num = num!(0.66666667); + assert_eq!(format!("{:.8}", a), "0.66666667"); + } } #[test] @@ -1265,7 +1618,7 @@ mod tests { #[test] fn test_macro_conversion() { - fn test_positive() { + fn test_positive() { let a: Num = num!(1.5); let one = A::one() << B; let b = Num::from_raw(one + (one >> 1)); @@ -1273,7 +1626,7 @@ mod tests { assert_eq!(a, b); } - fn test_negative() { + fn test_negative() { let a: Num = num!(-1.5); let one = A::one() << B; let b = Num::from_raw(one + (one >> 1)); @@ -1364,6 +1717,272 @@ mod tests { } } + #[test] + fn test_multiplication_overflow() { + let a: Num = Num::from_f32(0.625); + let b: Num = Num::from_f32(0.390625); + + assert_eq!(a * a, b); + + let a: Num = Num::from_f32(0.625); + let b: Num = Num::from_f32(0.390625); + + assert_eq!(a * a, b); + + let a: Num = Num::from_f32(0.625); + let b: Num = Num::from_f32(0.390625); + + assert_eq!(a * a, b); + + let a: Num = Num::from_f32(-0.625); + let b: Num = Num::from_f32(0.390625); + + assert_eq!(a * a, b); + + let a: Num = num!(0.625); + let b: Num = num!(0.390625); + + assert_eq!(a * a, b); + + let a: Num = num!(-0.625); + let b: Num = num!(0.390625); + + assert_eq!(a * a, b); + } + + mod overflow_strategies { + use super::*; + + macro_rules! test_overflow_strategies { + ($TestName: ident, $Type: ty, $Prec: literal) => { + #[test] + fn $TestName() { + let max_value_integer: Num<$Type, $Prec> = (<$Type>::MAX >> $Prec).into(); + let min_value_integer: Num<$Type, $Prec> = (<$Type>::MIN >> $Prec).into(); + let max_value_fract: Num<$Type, $Prec> = Num::from_raw(<$Type>::MAX); + let min_value_fract: Num<$Type, $Prec> = Num::from_raw(<$Type>::MIN); + + let max_minus_one: Num<$Type, $Prec> = ((<$Type>::MAX >> $Prec) - 1).into(); + let max_minus_two: Num<$Type, $Prec> = ((<$Type>::MAX >> $Prec) - 2).into(); + assert_eq!(max_minus_two.checked_add(1), Some(max_minus_one)); + assert_eq!(max_minus_two.checked_add(3), None); + assert_eq!(max_minus_two.overflowing_add(1), (max_minus_one, false)); + assert_eq!(max_minus_two.overflowing_add(3), (min_value_integer, true)); + assert_eq!(max_minus_two.saturating_add(1), max_minus_one); + assert_eq!(max_minus_two.saturating_add(3), max_value_fract); + assert_eq!(max_minus_two.wrapping_add(1), max_minus_one); + assert_eq!(max_minus_two.wrapping_add(3), min_value_integer); + + let eight: Num<$Type, $Prec> = 8.into(); + let four: Num<$Type, $Prec> = 4.into(); + // TODO: fix high precision division + let eight_low_precision: Num<$Type, 1> = 8.into(); + let four_low_precision: Num<$Type, 1> = 4.into(); + assert_eq!(eight_low_precision.checked_div(2), Some(four_low_precision)); + assert_eq!(eight.checked_div(0), None); + + let max_minus_two_times_two: Num<$Type, $Prec> = + ((<$Type>::MAX >> $Prec) - 5 | (1 << <$Type>::BITS - 1 - $Prec)).into(); + assert_eq!(max_minus_two.checked_mul(1), Some(max_minus_two)); + assert_eq!(four.checked_mul(2), Some(eight)); + assert_eq!(max_minus_two.checked_mul(2), None); + + assert_eq!(max_minus_two.overflowing_mul(1), (max_minus_two, false)); + assert_eq!(four.overflowing_mul(2), (eight, false)); + assert_eq!( + max_minus_two.overflowing_mul(2), + (max_minus_two_times_two, true) + ); + + assert_eq!(max_minus_two.saturating_mul(1), max_minus_two); + assert_eq!(four.saturating_mul(2), eight); + assert_eq!(max_minus_two.saturating_mul(2), max_value_fract); + + assert_eq!(max_minus_two.wrapping_mul(1), max_minus_two); + assert_eq!(four.wrapping_mul(2), eight); + assert_eq!(max_minus_two.wrapping_mul(2), max_minus_two_times_two); + + let min_plus_one: Num<$Type, $Prec> = ((<$Type>::MIN >> $Prec) + 1).into(); + let min_plus_two: Num<$Type, $Prec> = ((<$Type>::MIN >> $Prec) + 2).into(); + assert_eq!(min_plus_two.checked_sub(1), Some(min_plus_one)); + assert_eq!(min_plus_two.checked_sub(3), None); + assert_eq!(min_plus_two.overflowing_sub(1), (min_plus_one, false)); + assert_eq!(min_plus_two.overflowing_sub(3), (max_value_integer, true)); + assert_eq!(min_plus_two.saturating_sub(1), min_plus_one); + assert_eq!(min_plus_two.saturating_sub(3), min_value_fract); + assert_eq!(min_plus_two.wrapping_sub(1), min_plus_one); + assert_eq!(min_plus_two.wrapping_sub(3), max_value_integer); + } + }; + } + + macro_rules! test_overflow_strategies_signed_mul { + ($TestName: ident, $Type: ty, $Prec: literal) => { + #[test] + fn $TestName() { + let two_point_five: Num<$Type, $Prec> = Num::from_f32(2.5); + let five: Num<$Type, $Prec> = 5.into(); + let minus_two_point_five: Num<$Type, $Prec> = Num::from_f32(-2.5); + let minus_six_point_twenty_five: Num<$Type, $Prec> = Num::from_f32(6.25); + assert_eq!(two_point_five.checked_mul(-2), Some(-five)); + assert_eq!( + minus_two_point_five.checked_mul(minus_two_point_five), + Some(minus_six_point_twenty_five) + ); + assert_eq!(two_point_five.overflowing_mul(-2), (-five, false)); + assert_eq!( + minus_two_point_five.overflowing_mul(minus_two_point_five), + (minus_six_point_twenty_five, false) + ); + assert_eq!(two_point_five.saturating_mul(-2), -five); + assert_eq!( + minus_two_point_five.saturating_mul(minus_two_point_five), + minus_six_point_twenty_five + ); + assert_eq!(two_point_five.wrapping_mul(-2), -five); + assert_eq!( + minus_two_point_five.wrapping_mul(minus_two_point_five), + minus_six_point_twenty_five + ); + + let very_small: Num<$Type, $Prec> = + (<$Type>::MIN + (2 << $Prec) >> $Prec + 1).into(); + let very_small_times_minus_thirty_two: Num<$Type, $Prec> = (-32).into(); + let negative_very_small_squared: Num<$Type, $Prec> = + (<$Type>::MAX >> $Prec).into(); + assert_eq!(very_small.checked_mul(-32), None); + assert_eq!(very_small.checked_mul(very_small), None); + assert_eq!( + very_small.overflowing_mul(-32), + (very_small_times_minus_thirty_two, true) + ); + assert_eq!( + very_small.overflowing_mul(-very_small), + (negative_very_small_squared, true) + ); + assert_eq!(very_small.saturating_mul(-32), Num::from_raw(<$Type>::MAX)); + assert_eq!( + very_small.saturating_mul(-very_small), + Num::from_raw(<$Type>::MIN) + ); + assert_eq!( + very_small.wrapping_mul(-32), + very_small_times_minus_thirty_two + ); + assert_eq!( + very_small.wrapping_mul(-very_small), + negative_very_small_squared + ); + } + }; + } + + macro_rules! test_panic_on_overflow { + ($ModName: ident, $Type: ty, $Prec: literal) => { + mod $ModName { + use super::*; + + #[test] + #[cfg_attr( + debug_assertions, + should_panic(expected = "attempt to add with overflow") + )] + fn add() { + let max_minus_two: Num<$Type, $Prec> = ((<$Type>::MAX >> $Prec) - 2).into(); + let three: Num<$Type, $Prec> = 3.into(); + let _ = max_minus_two + three; + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn div() { + let eight: Num<$Type, 1> = 8.into(); + let zero: Num<$Type, 1> = 0.into(); + let _ = eight / zero; + } + + #[test] + #[cfg_attr( + debug_assertions, + should_panic(expected = "attempt to multiply with overflow") + )] + fn mul() { + let max_minus_two: Num<$Type, $Prec> = ((<$Type>::MAX >> $Prec) - 2).into(); + let two: Num<$Type, $Prec> = 2.into(); + let _ = max_minus_two * two; + } + + #[test] + #[cfg_attr( + debug_assertions, + should_panic(expected = "attempt to subtract with overflow") + )] + fn sub() { + let min_plus_two: Num<$Type, $Prec> = ((<$Type>::MIN >> $Prec) + 2).into(); + let three: Num<$Type, $Prec> = 3.into(); + let _ = min_plus_two - three; + } + } + }; + } + + macro_rules! test_panic_on_overflow_signed_mul { + ($ModName: ident, $Type: ty, $Prec: literal) => { + mod $ModName { + use super::*; + + #[test] + #[cfg_attr( + debug_assertions, + should_panic(expected = "attempt to multiply with overflow") + )] + fn mul_negative_times_positive() { + let very_small: Num<$Type, $Prec> = + (<$Type>::MIN + (2 << $Prec) >> $Prec).into(); + let _ = very_small * -very_small; + } + + #[test] + #[cfg_attr( + debug_assertions, + should_panic(expected = "attempt to multiply with overflow") + )] + fn mul_negative_times_negative() { + let very_small: Num<$Type, $Prec> = + (<$Type>::MIN + (2 << $Prec) >> $Prec).into(); + let minus_thirty_two: Num<$Type, $Prec> = (-32).into(); + let _ = very_small * minus_thirty_two; + } + } + }; + } + + test_overflow_strategies!(test_i8, i8, 1); + test_overflow_strategies!(test_u8, u8, 1); + test_overflow_strategies!(test_i16, i16, 3); + test_overflow_strategies!(test_u16, u16, 3); + test_overflow_strategies!(test_i32, i32, 3); + test_overflow_strategies!(test_i32_high_precision, i32, 18); + test_overflow_strategies!(test_u32, u32, 3); + test_overflow_strategies!(test_u32_high_precision, u32, 18); + test_overflow_strategies_signed_mul!(test_i8_signed_mul, i8, 2); + test_overflow_strategies_signed_mul!(test_i16_signed_mul, i16, 3); + test_overflow_strategies_signed_mul!(test_i32_signed_mul, i32, 3); + test_overflow_strategies_signed_mul!(test_i32_high_precision_signed_mul, i32, 18); + test_panic_on_overflow!(test_panic_i8, i8, 2); + test_panic_on_overflow!(test_panic_u8, u8, 2); + test_panic_on_overflow!(test_panic_i16, i16, 3); + test_panic_on_overflow!(test_panic_u16, u16, 3); + test_panic_on_overflow!(test_panic_i32, i32, 3); + test_panic_on_overflow!(test_panic_i32_high_precision, i32, 18); + test_panic_on_overflow!(test_panic_u32, u32, 3); + test_panic_on_overflow!(test_panic_u32_high_precision, i32, 18); + test_panic_on_overflow_signed_mul!(test_panic_i8_signed_mul, i8, 2); + test_panic_on_overflow_signed_mul!(test_panic_i16_signed_mul, i16, 3); + test_panic_on_overflow_signed_mul!(test_panic_i32_signed_mul, i32, 3); + test_panic_on_overflow_signed_mul!(test_panic_i32_high_precision_signed_mul, i32, 18); + } + #[test] fn test_division_by_2_and_15() { let two: Num = 2.into(); diff --git a/agb-macros/src/lib.rs b/agb-macros/src/lib.rs index f2396714c..e3619134b 100644 --- a/agb-macros/src/lib.rs +++ b/agb-macros/src/lib.rs @@ -131,12 +131,14 @@ pub fn num(input: TokenStream) -> TokenStream { let f = syn::parse_macro_input!(input as syn::LitFloat); let v: f64 = f.base10_parse().expect("The number should be parsable"); - let integer = v.trunc(); - let fractional = v.fract() * (1_u64 << 30) as f64; - - let integer = integer as i32; - let fractional = fractional as i32; - quote!((#integer, #fractional)).into() + let integer = v.trunc().abs(); + let fractional = v.fract().abs() * (1_u64 << 32) as f64; + let sign = v.signum(); + + let integer = integer as u32; + let fractional = fractional as u32; + let sign = sign as i8; + quote!((#sign, #integer, #fractional)).into() } fn hashed_ident(f: &T) -> Ident { diff --git a/agb/src/input.rs b/agb/src/input.rs index 15cf3c31c..747a0626b 100644 --- a/agb/src/input.rs +++ b/agb/src/input.rs @@ -145,7 +145,7 @@ impl ButtonController { #[must_use] pub fn vector(&self) -> Vector2D where - T: From + crate::fixnum::FixedWidthUnsignedInteger, + T: From + crate::fixnum::FixedWidthInteger, { (self.x_tri() as i32, self.y_tri() as i32).into() } @@ -176,7 +176,7 @@ impl ButtonController { /// Returns a vector which represents the direction the button was just pressed in. pub fn just_pressed_vector(&self) -> Vector2D where - T: From + crate::fixnum::FixedWidthUnsignedInteger, + T: From + crate::fixnum::FixedWidthInteger, { ( self.just_pressed_x_tri() as i32,