From b02f5feceea0b2137899706c8c38ae3f758e9714 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 15:40:54 +0200 Subject: [PATCH 01/11] Fix upcast multiplication and use native arm long multiplication when possible --- agb-fixnum/src/lib.rs | 93 +++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index f8bba06e1..86ce8ac14 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -83,26 +83,56 @@ macro_rules! fixed_width_unsigned_integer_impl { } macro_rules! upcast_multiply_impl { - ($T: ty, optimised_64_bit) => { + ($T: ty, optimised_64_bit_signed) => { + #[cfg(not(target_arch = "arm"))] #[inline(always)] fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { - use num_traits::One; - - let mask = (Self::one() << n).wrapping_sub(1); - - let a_floor = a >> n; - let a_frac = a & mask; + (((a as i64) * (b as i64)) >> n) as $T + } - let b_floor = b >> n; - let b_frac = b & mask; + #[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(a: Self, b: Self, n: usize) -> Self { + use core::arch::asm; + let low: u32; + let high: u32; + unsafe { + asm!( + "SMULL {low}, {high}, {a}, {b}", + a = in(reg) a, + b = in(reg) b, + low = lateout(reg) low, + high = lateout(reg) high, + ); + } + ((low >> n) | (high << (32 - n))) as $T + } + }; + ($T: ty, optimised_64_bit_unsigned) => { + #[cfg(not(target_arch = "arm"))] + #[inline(always)] + fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { + (((a as u64) * (b as u64)) >> n) as $T + } - (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) + #[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(a: Self, b: Self, n: usize) -> Self { + 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 >> n) | (high << (32 - n))) as $T } }; ($T: ty, $Upcast: ty) => { @@ -117,8 +147,8 @@ fixed_width_unsigned_integer_impl!(u8, u32); fixed_width_unsigned_integer_impl!(i16, i32); fixed_width_unsigned_integer_impl!(u16, u32); -fixed_width_unsigned_integer_impl!(i32, optimised_64_bit); -fixed_width_unsigned_integer_impl!(u32, optimised_64_bit); +fixed_width_unsigned_integer_impl!(i32, optimised_64_bit_signed); +fixed_width_unsigned_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)] @@ -369,14 +399,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 @@ -1378,6 +1408,29 @@ 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); + } + #[test] fn test_division_by_2_and_15() { let two: Num = 2.into(); From 9c95abebbcc3736db1847cb53d1acdcee8220246 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 19:56:25 +0200 Subject: [PATCH 02/11] Fix num!() for high precision numbers --- agb-fixnum/src/lib.rs | 26 +++++++++++++++++++++----- agb-macros/src/lib.rs | 14 ++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 86ce8ac14..ee047153b 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -182,10 +182,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, + ))) } } @@ -482,8 +487,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)) } } @@ -1429,6 +1435,16 @@ mod tests { 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); } #[test] diff --git a/agb-macros/src/lib.rs b/agb-macros/src/lib.rs index 2c898f925..650ae715d 100644 --- a/agb-macros/src/lib.rs +++ b/agb-macros/src/lib.rs @@ -133,12 +133,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 { From 52ce30c56eae10794d53959300914a2e5338a403 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 21:27:48 +0200 Subject: [PATCH 03/11] Simplify FixedWidthUnsignedInteger name and trait bounds --- agb-fixnum/src/lib.rs | 105 ++++++++++++++++++------------------------ agb/src/input.rs | 4 +- 2 files changed, 47 insertions(+), 62 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index ee047153b..0d0bd81ca 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -3,15 +3,12 @@ //! 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::Signed; +use num_traits::{One, PrimInt, Signed, Zero}; #[doc(hidden)] /// Used internally by the [num!] macro which should be used instead. @@ -35,23 +32,11 @@ macro_rules! num { /// fixed point number. pub trait Number: Copy + PartialOrd + Ord + num_traits::Num {} -impl Number for Num {} -impl Number for I {} +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 -{ +pub trait FixedWidthInteger: Number + PrimInt + Display { /// Returns the representation of ten fn ten() -> Self; /// Converts an i32 to it's own representation, panics on failure @@ -61,13 +46,13 @@ pub trait FixedWidthUnsignedInteger: } /// Trait for an integer that includes negation -pub trait FixedWidthSignedInteger: FixedWidthUnsignedInteger + num_traits::sign::Signed {} +pub trait FixedWidthSignedInteger: FixedWidthInteger + Signed {} -impl FixedWidthSignedInteger for I {} +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 { + impl FixedWidthInteger for $T { #[inline(always)] fn ten() -> Self { 10 @@ -143,20 +128,20 @@ macro_rules! upcast_multiply_impl { }; } -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!(u8, u32); +fixed_width_integer_impl!(i16, i32); +fixed_width_integer_impl!(u16, u32); -fixed_width_unsigned_integer_impl!(i32, optimised_64_bit_signed); -fixed_width_unsigned_integer_impl!(u32, optimised_64_bit_unsigned); +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)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[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()) } @@ -166,13 +151,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 { @@ -198,7 +183,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) } @@ -206,7 +191,7 @@ impl From for Num { impl Default for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { fn default() -> Self { Num(I::zero()) @@ -215,7 +200,7 @@ where impl Add for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { type Output = Self; @@ -226,7 +211,7 @@ where impl AddAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { fn add_assign(&mut self, rhs: T) { @@ -236,7 +221,7 @@ where impl Sub for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { type Output = Self; @@ -247,7 +232,7 @@ where impl SubAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { fn sub_assign(&mut self, rhs: T) { @@ -257,7 +242,7 @@ where impl Mul> for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn mul(self, rhs: Num) -> Self::Output { @@ -267,7 +252,7 @@ where impl Mul for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn mul(self, rhs: I) -> Self::Output { @@ -277,7 +262,7 @@ where impl MulAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, Num: Mul>, { fn mul_assign(&mut self, rhs: T) { @@ -287,7 +272,7 @@ where impl Div> for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn div(self, rhs: Num) -> Self::Output { @@ -297,7 +282,7 @@ where impl Div for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, { type Output = Self; fn div(self, rhs: I) -> Self::Output { @@ -307,7 +292,7 @@ where impl DivAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, Num: Div>, { fn div_assign(&mut self, rhs: T) { @@ -317,7 +302,7 @@ where impl Rem for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { type Output = Self; @@ -328,7 +313,7 @@ where impl RemAssign for Num where - I: FixedWidthUnsignedInteger, + I: FixedWidthInteger, T: Into>, { fn rem_assign(&mut self, modulus: T) { @@ -343,9 +328,9 @@ impl Neg for Num { } } -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)) @@ -366,7 +351,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::() { @@ -582,12 +567,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::one() + I::one() + I::one() + I::one(); (self - one / four).cos() } } -impl num_traits::sign::Signed for Num { +impl Signed for Num { fn abs(&self) -> Self { Self::abs(*self) } @@ -609,7 +594,7 @@ impl num_traits::sign::Signed for Nu } } -impl Display for Num { +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(); @@ -676,7 +661,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; @@ -806,7 +791,7 @@ impl Vector2D { } } -impl Vector2D> { +impl Vector2D> { #[must_use] /// Truncates the x and y coordinate, see [Num::trunc] /// ``` @@ -839,7 +824,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( @@ -936,7 +921,7 @@ impl Vector2D> { } } -impl From> for Vector2D> { +impl From> for Vector2D> { fn from(n: Vector2D) -> Self { Vector2D { x: n.x.into(), @@ -1068,7 +1053,7 @@ impl Rect { } } -impl Rect { +impl Rect { /// Iterate over the points in a rectangle in row major order. /// ``` /// use agb_fixnum::{Rect, vec2}; @@ -1315,7 +1300,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)); 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, From dde601eaa730f289707875e16dadff07469f50d8 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 22:48:26 +0200 Subject: [PATCH 04/11] Fix print for high precision numbers --- agb-fixnum/src/lib.rs | 54 +++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 0d0bd81ca..19c95a099 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -596,42 +596,44 @@ impl Signed for Num { 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, "-")?; } @@ -641,19 +643,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; } } @@ -1288,6 +1290,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] From 3b510027da1f31b1453cee3dfe6b547c453e201c Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 22:55:13 +0200 Subject: [PATCH 05/11] Small clean up --- agb-fixnum/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 19c95a099..da2721ef6 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -37,8 +37,6 @@ impl Number for I {} /// A trait for integers that don't implement unary negation pub trait FixedWidthInteger: Number + PrimInt + 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 @@ -53,10 +51,6 @@ impl FixedWidthSignedInteger for I {} macro_rules! fixed_width_integer_impl { ($T: ty, $Upcast: ident) => { impl FixedWidthInteger for $T { - #[inline(always)] - fn ten() -> Self { - 10 - } #[inline(always)] fn from_as_i32(v: i32) -> Self { v as $T @@ -567,7 +561,7 @@ impl Num { #[must_use] pub fn sin(self) -> Self { let one: Self = I::one().into(); - let four: I = I::one() + I::one() + I::one() + I::one(); + let four: I = I::from_as_i32(4); (self - one / four).cos() } } From dde3f1249caecaa88117ed221eb1253fb5e3fa9c Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 23:00:17 +0200 Subject: [PATCH 06/11] Reduce unneeded upcast size for u8 and add support to i8 --- agb-fixnum/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index da2721ef6..ee60b7bf8 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -122,7 +122,8 @@ macro_rules! upcast_multiply_impl { }; } -fixed_width_integer_impl!(u8, 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); From 3ea03c9ad1df9933d1044630368ecf28faee53a5 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Tue, 21 May 2024 23:19:46 +0200 Subject: [PATCH 07/11] Remove FixedWidthSignedInteger --- agb-fixnum/src/lib.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index ee60b7bf8..b090ed921 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -35,7 +35,7 @@ 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 +/// A trait for integers with fixed width pub trait FixedWidthInteger: Number + PrimInt + Display { /// Converts an i32 to it's own representation, panics on failure fn from_as_i32(v: i32) -> Self; @@ -43,11 +43,6 @@ pub trait FixedWidthInteger: Number + PrimInt + Display { fn upcast_multiply(a: Self, b: Self, n: usize) -> Self; } -/// Trait for an integer that includes negation -pub trait FixedWidthSignedInteger: FixedWidthInteger + Signed {} - -impl FixedWidthSignedInteger for I {} - macro_rules! fixed_width_integer_impl { ($T: ty, $Upcast: ident) => { impl FixedWidthInteger for $T { @@ -316,7 +311,7 @@ where } } -impl Neg for Num { +impl Neg for Num { type Output = Self; fn neg(self) -> Self::Output { Num(-self.0) @@ -507,7 +502,7 @@ impl Num { } } -impl Num { +impl Num { #[must_use] /// Returns the absolute value of a fixed point number /// ``` @@ -567,7 +562,7 @@ impl Num { } } -impl Signed for Num { +impl Signed for Num { fn abs(&self) -> Self { Self::abs(*self) } @@ -902,7 +897,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]. /// ``` @@ -1317,7 +1312,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)); From 89f45dabe4d7daf2c07e713cac30835f56f4717d Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Thu, 23 May 2024 20:33:49 +0200 Subject: [PATCH 08/11] Implement the various checked, overflowing, saturating and wrapping operations --- agb-fixnum/src/lib.rs | 521 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 486 insertions(+), 35 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index b090ed921..5f8aef8d0 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -8,7 +8,10 @@ use core::{ mem::size_of, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, }; -use num_traits::{One, PrimInt, Signed, Zero}; +use num_traits::{ + ops::overflowing::{OverflowingAdd, OverflowingSub}, + One, PrimInt, Signed, WrappingAdd, WrappingSub, Zero, +}; #[doc(hidden)] /// Used internally by the [num!] macro which should be used instead. @@ -36,11 +39,21 @@ impl Number for Num {} impl Number for I {} /// A trait for integers with fixed width -pub trait FixedWidthInteger: Number + PrimInt + Display { +pub trait FixedWidthInteger: + Number + PrimInt + OverflowingAdd + OverflowingSub + WrappingAdd + WrappingSub + Display +{ /// 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; + /// Returns Some((a * b) >> N) if the multiplication didn't overflowed + fn upcast_multiply_checked(a: Self, b: Self, n: usize) -> Option; + /// Returns ((a * b) >> N, flag), where flag is true if the operation overflowed + fn upcast_multiply_overflowing(a: Self, b: Self, n: usize) -> (Self, bool); + /// Returns (a * b) >> N, saturating at the numeric bounds instead of overflowing + fn upcast_multiply_saturating(a: Self, b: Self, n: usize) -> Self; + /// Returns (a * b) >> N, but doesn't panic in case of overflow in debug mode + fn upcast_multiply_wrapping(a: Self, b: Self, n: usize) -> Self; } macro_rules! fixed_width_integer_impl { @@ -56,63 +69,208 @@ macro_rules! fixed_width_integer_impl { }; } +#[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) +} + +#[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_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, n: usize) -> Self { - (((a as i64) * (b as i64)) >> n) as $T + 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); + if cfg!(debug_assertions) && is_overflow { + panic!("attempt to multiply with overflow"); + } + res } #[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(a: Self, b: Self, n: usize) -> Self { - use core::arch::asm; - let low: u32; - let high: u32; - unsafe { - asm!( - "SMULL {low}, {high}, {a}, {b}", - a = in(reg) a, - b = in(reg) b, - low = lateout(reg) low, - high = lateout(reg) high, - ); + #[inline(always)] + fn upcast_multiply_checked(a: Self, b: Self, n: usize) -> Option { + 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); + if is_overflow { + None + } else { + Some(res) } - ((low >> n) | (high << (32 - n))) as $T + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_overflowing(a: Self, b: Self, n: usize) -> (Self, bool) { + 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, n: usize) -> Self { + 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); + 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, n: usize) -> Self { + 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"))] - #[inline(always)] + upcast_multiply_impl!($T, u64); + + #[cfg(target_arch = "arm")] fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { - (((a as u64) * (b as u64)) >> n) as $T + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let is_overflow = (high >> n) != 0; + if cfg!(debug_assertions) && is_overflow { + panic!("attempt to multiply with overflow"); + } + ((low >> n) | (high << (32 - n))) as $T } #[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(a: Self, b: Self, n: usize) -> Self { - 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, - ); + #[inline(always)] + fn upcast_multiply_checked(a: Self, b: Self, n: usize) -> Option { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let is_overflow = (high >> n) != 0; + if is_overflow { + None + } else { + Some(((low >> n) | (high << (32 - n))) as $T) } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_overflowing(a: Self, b: Self, n: usize) -> (Self, bool) { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let is_overflow = (high >> n) != 0; + (((low >> n) | (high << (32 - n))) as $T, is_overflow) + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_saturating(a: Self, b: Self, n: usize) -> Self { + let (low, high) = upcast_multiply_wide_unsigned(a, b); + let is_overflow = (high >> n) != 0; + if is_overflow { + <$T>::MAX + } else { + ((low >> n) | (high << (32 - n))) as $T + } + } + + #[cfg(target_arch = "arm")] + #[inline(always)] + fn upcast_multiply_wrapping(a: Self, b: Self, n: usize) -> Self { + 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 + 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, n: usize) -> 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, n: usize) -> (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, n: usize) -> 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, n: usize) -> Self { + ((a as $Upcast) * (b as $Upcast) >> n) as $T } }; } @@ -584,6 +742,79 @@ impl Signed 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, N).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, N); + (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, N)) + } + + /// 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, N)) + } + + /// 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 repr = self.0.to_i64().expect("Num's I can always be converted to i64"); @@ -1436,6 +1667,226 @@ mod tests { assert_eq!(a * a, b); } + mod overflow_strategies { + use super::*; + + macro_rules! test_overflow_strategies { + ($TestName: ident, $Type: ty) => { + #[test] + fn $TestName() { + let max_value_integer: Num<$Type, 3> = (<$Type>::MAX >> 3).into(); + let min_value_integer: Num<$Type, 3> = (<$Type>::MIN >> 3).into(); + let max_value_fract: Num<$Type, 3> = Num::from_raw(<$Type>::MAX); + let min_value_fract: Num<$Type, 3> = Num::from_raw(<$Type>::MIN); + + let max_minus_one: Num<$Type, 3> = ((<$Type>::MAX >> 3) - 1).into(); + let max_minus_two: Num<$Type, 3> = ((<$Type>::MAX >> 3) - 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, 1> = 8.into(); + let four: Num<$Type, 1> = 4.into(); + assert_eq!(eight.checked_div(2), Some(four)); + assert_eq!(eight.checked_div(0), None); + + let max_minus_two_times_two: Num<$Type, 3> = + ((<$Type>::MAX >> 3) - 5 | (1 << <$Type>::BITS - 1 - 3)).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, 3> = ((<$Type>::MIN >> 3) + 1).into(); + let min_plus_two: Num<$Type, 3> = ((<$Type>::MIN >> 3) + 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) => { + #[test] + fn $TestName() { + let two_point_five: Num<$Type, 3> = Num::from_f32(2.5); + let five: Num<$Type, 3> = 5.into(); + let minus_two_point_five: Num<$Type, 3> = Num::from_f32(-2.5); + let minus_six_point_twenty_five: Num<$Type, 3> = 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, 2> = (<$Type>::MIN + 8 >> 3).into(); + let very_small_times_minus_thirty_two: Num<$Type, 2> = (-32).into(); + let negative_very_small_squared: Num<$Type, 2> = (<$Type>::MAX >> 2).into(); + assert_eq!(very_small.checked_mul(-4), 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(-4), 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) => { + 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, 3> = ((<$Type>::MAX >> 3) - 2).into(); + let three: Num<$Type, 3> = 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, 3> = ((<$Type>::MAX >> 3) - 2).into(); + let two: Num<$Type, 3> = 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, 3> = ((<$Type>::MIN >> 3) + 2).into(); + let three: Num<$Type, 3> = 3.into(); + let _ = min_plus_two - three; + } + } + }; + } + + macro_rules! test_panic_on_overflow_signed_mul { + ($ModName: ident, $Type: ty) => { + 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, 2> = (<$Type>::MIN + 8 >> 3).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, 2> = (<$Type>::MIN + 8 >> 3).into(); + let minus_thirty_two: Num<$Type, 2> = (-32).into(); + let _ = very_small * minus_thirty_two; + } + } + }; + } + + test_overflow_strategies!(test_i8, i8); + test_overflow_strategies!(test_u8, u8); + test_overflow_strategies!(test_i16, i16); + test_overflow_strategies!(test_u16, u16); + test_overflow_strategies!(test_i32, i32); + test_overflow_strategies!(test_u32, u32); + test_overflow_strategies_signed_mul!(test_i8_signed_mul, i8); + test_overflow_strategies_signed_mul!(test_i16_signed_mul, i16); + test_overflow_strategies_signed_mul!(test_i32_signed_mul, i32); + test_panic_on_overflow!(test_panic_i8, i8); + test_panic_on_overflow!(test_panic_u8, u8); + test_panic_on_overflow!(test_panic_i16, i16); + test_panic_on_overflow!(test_panic_u16, u16); + test_panic_on_overflow!(test_panic_i32, i32); + test_panic_on_overflow!(test_panic_u32, u32); + test_panic_on_overflow_signed_mul!(test_panic_i8_signed_mul, i8); + test_panic_on_overflow_signed_mul!(test_panic_i16_signed_mul, i16); + test_panic_on_overflow_signed_mul!(test_panic_i32_signed_mul, i32); + } + #[test] fn test_division_by_2_and_15() { let two: Num = 2.into(); From d548665f3a4a2a96d96fd20d685714d1cd6a7938 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Fri, 24 May 2024 00:02:59 +0200 Subject: [PATCH 09/11] Make the CI happy --- agb-fixnum/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 5f8aef8d0..08bb3f7db 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -817,7 +817,10 @@ impl Num { impl Display for Num { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let repr = self.0.to_i64().expect("Num's I can always be converted to i64"); + 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; From 2e9ccfb3353399e678e68d5c250ad5bb2629440c Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Sun, 26 May 2024 23:18:35 +0200 Subject: [PATCH 10/11] Make the "n" argument in upcast_multiply* a const generic --- agb-fixnum/src/lib.rs | 96 +++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 08bb3f7db..ed7f9a5b1 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -45,15 +45,15 @@ pub trait FixedWidthInteger: /// 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, n: usize) -> Option; + 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, n: usize) -> (Self, bool); + 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, n: usize) -> Self; + 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, n: usize) -> Self; + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self; } macro_rules! fixed_width_integer_impl { @@ -114,10 +114,10 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { + fn upcast_multiply(a: Self, b: Self) -> Self { 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); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); if cfg!(debug_assertions) && is_overflow { panic!("attempt to multiply with overflow"); } @@ -126,10 +126,10 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_checked(a: Self, b: Self, n: usize) -> Option { + fn upcast_multiply_checked(a: Self, b: Self) -> Option { 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); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); if is_overflow { None } else { @@ -139,19 +139,19 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_overflowing(a: Self, b: Self, n: usize) -> (Self, bool) { + fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { 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); + 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, n: usize) -> Self { + fn upcast_multiply_saturating(a: Self, b: Self) -> Self { 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); + let res = ((low >> N) | (high << (32 - N)) as u32) as $T; + let is_overflow = (high >> N) != (res >> 31); if is_overflow { if (a < 0) ^ (b < 0) { <$T>::MIN @@ -165,9 +165,9 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_wrapping(a: Self, b: Self, n: usize) -> Self { + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { let (low, high) = upcast_multiply_wide_signed(a, b); - ((low >> n) | (high << (32 - n)) as u32) as $T + ((low >> N) | (high << (32 - N)) as u32) as $T } }; ($T: ty, optimised_64_bit_unsigned) => { @@ -175,58 +175,58 @@ macro_rules! upcast_multiply_impl { upcast_multiply_impl!($T, u64); #[cfg(target_arch = "arm")] - fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { + fn upcast_multiply(a: Self, b: Self) -> Self { let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> n) != 0; + let is_overflow = (high >> N) != 0; if cfg!(debug_assertions) && is_overflow { panic!("attempt to multiply with overflow"); } - ((low >> n) | (high << (32 - n))) as $T + ((low >> N) | (high << (32 - N))) as $T } #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_checked(a: Self, b: Self, n: usize) -> Option { + fn upcast_multiply_checked(a: Self, b: Self) -> Option { let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> n) != 0; + let is_overflow = (high >> N) != 0; if is_overflow { None } else { - Some(((low >> n) | (high << (32 - n))) as $T) + Some(((low >> N) | (high << (32 - N))) as $T) } } #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_overflowing(a: Self, b: Self, n: usize) -> (Self, bool) { + fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> n) != 0; - (((low >> n) | (high << (32 - n))) as $T, is_overflow) + let is_overflow = (high >> N) != 0; + (((low >> N) | (high << (32 - N))) as $T, is_overflow) } #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_saturating(a: Self, b: Self, n: usize) -> Self { + fn upcast_multiply_saturating(a: Self, b: Self) -> Self { let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> n) != 0; + let is_overflow = (high >> N) != 0; if is_overflow { <$T>::MAX } else { - ((low >> n) | (high << (32 - n))) as $T + ((low >> N) | (high << (32 - N))) as $T } } #[cfg(target_arch = "arm")] #[inline(always)] - fn upcast_multiply_wrapping(a: Self, b: Self, n: usize) -> Self { + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { let (low, high) = upcast_multiply_wide_unsigned(a, b); - ((low >> n) | (high << (32 - n))) as $T + ((low >> N) | (high << (32 - N))) as $T } }; ($T: ty, $Upcast: ty) => { #[inline(always)] - fn upcast_multiply(a: Self, b: Self, n: usize) -> Self { - let res = ((a as $Upcast) * (b as $Upcast)) >> n; + 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"); @@ -235,8 +235,8 @@ macro_rules! upcast_multiply_impl { } #[inline(always)] - fn upcast_multiply_checked(a: Self, b: Self, n: usize) -> Option { - let res = ((a as $Upcast) * (b as $Upcast)) >> n; + 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 @@ -246,15 +246,15 @@ macro_rules! upcast_multiply_impl { } #[inline(always)] - fn upcast_multiply_overflowing(a: Self, b: Self, n: usize) -> (Self, bool) { - let res = ((a as $Upcast) * (b as $Upcast)) >> n; + 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, n: usize) -> Self { - let res = ((a as $Upcast) * (b as $Upcast)) >> n; + 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)] @@ -269,8 +269,8 @@ macro_rules! upcast_multiply_impl { } #[inline(always)] - fn upcast_multiply_wrapping(a: Self, b: Self, n: usize) -> Self { - ((a as $Upcast) * (b as $Upcast) >> n) as $T + fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { + ((a as $Upcast) * (b as $Upcast) >> N) as $T } }; } @@ -394,7 +394,7 @@ where { 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)) } } @@ -755,7 +755,7 @@ impl Num { /// 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, N).map(|n| Num(n)) + I::upcast_multiply_checked::(self.0, rhs.into().0).map(|n| Num(n)) } /// Checked integer subtraction. Computes self - rhs, returning None if overflow occurred @@ -773,7 +773,7 @@ impl Num { /// 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, N); + let (res, flag) = I::upcast_multiply_overflowing::(self.0, rhs.into().0); (Num(res), flag) } @@ -791,7 +791,7 @@ impl Num { /// 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, N)) + Num(I::upcast_multiply_saturating::(self.0, rhs.into().0)) } /// Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds instead of overflowing @@ -806,7 +806,7 @@ impl Num { /// 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, N)) + Num(I::upcast_multiply_wrapping::(self.0, rhs.into().0)) } /// Wrapping (modular) subtraction. Computes self - rhs, wrapping around at the boundary of the type From 9ed9a5b88858e115cfae091a07de1dff82c41c61 Mon Sep 17 00:00:00 2001 From: Chiptun3r <170269616+Chiptun3r@users.noreply.github.com> Date: Mon, 27 May 2024 22:46:35 +0200 Subject: [PATCH 11/11] Add fast multiplication for low precision 32bits fixnums --- agb-fixnum/src/lib.rs | 271 +++++++++++++++++++++++++++++------------- 1 file changed, 189 insertions(+), 82 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index ed7f9a5b1..2317c3e28 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -107,6 +107,50 @@ fn upcast_multiply_wide_signed(a: i32, b: i32) -> (u32, i32) { (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_frac = a & mask; + + let b_floor = b >> N; + let b_frac = b & mask; + + 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"))] @@ -115,9 +159,14 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply(a: Self, b: Self) -> Self { - 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); + 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"); } @@ -127,9 +176,14 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_checked(a: Self, b: Self) -> Option { - 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); + 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 { @@ -140,18 +194,27 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { - 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 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 (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); + 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 @@ -166,8 +229,12 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { - let (low, high) = upcast_multiply_wide_signed(a, b); - ((low >> N) | (high << (32 - N)) as u32) as $T + 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) => { @@ -176,51 +243,78 @@ macro_rules! upcast_multiply_impl { #[cfg(target_arch = "arm")] fn upcast_multiply(a: Self, b: Self) -> Self { - let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> N) != 0; + 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"); } - ((low >> N) | (high << (32 - N))) as $T + res } #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_checked(a: Self, b: Self) -> Option { - let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> N) != 0; + 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(((low >> N) | (high << (32 - N))) as $T) + Some(res) } } #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_overflowing(a: Self, b: Self) -> (Self, bool) { - let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> N) != 0; - (((low >> N) | (high << (32 - N))) as $T, 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) + } } #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_saturating(a: Self, b: Self) -> Self { - let (low, high) = upcast_multiply_wide_unsigned(a, b); - let is_overflow = (high >> N) != 0; + 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 { - ((low >> N) | (high << (32 - N))) as $T + res } } #[cfg(target_arch = "arm")] #[inline(always)] fn upcast_multiply_wrapping(a: Self, b: Self) -> Self { - let (low, high) = upcast_multiply_wide_unsigned(a, b); - ((low >> N) | (high << (32 - N))) as $T + 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) => { @@ -1674,16 +1768,16 @@ mod tests { use super::*; macro_rules! test_overflow_strategies { - ($TestName: ident, $Type: ty) => { + ($TestName: ident, $Type: ty, $Prec: literal) => { #[test] fn $TestName() { - let max_value_integer: Num<$Type, 3> = (<$Type>::MAX >> 3).into(); - let min_value_integer: Num<$Type, 3> = (<$Type>::MIN >> 3).into(); - let max_value_fract: Num<$Type, 3> = Num::from_raw(<$Type>::MAX); - let min_value_fract: Num<$Type, 3> = Num::from_raw(<$Type>::MIN); + 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, 3> = ((<$Type>::MAX >> 3) - 1).into(); - let max_minus_two: Num<$Type, 3> = ((<$Type>::MAX >> 3) - 2).into(); + 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)); @@ -1693,13 +1787,16 @@ mod tests { 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, 1> = 8.into(); - let four: Num<$Type, 1> = 4.into(); - assert_eq!(eight.checked_div(2), Some(four)); + 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, 3> = - ((<$Type>::MAX >> 3) - 5 | (1 << <$Type>::BITS - 1 - 3)).into(); + 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); @@ -1719,8 +1816,8 @@ mod tests { 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, 3> = ((<$Type>::MIN >> 3) + 1).into(); - let min_plus_two: Num<$Type, 3> = ((<$Type>::MIN >> 3) + 2).into(); + 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)); @@ -1734,13 +1831,13 @@ mod tests { } macro_rules! test_overflow_strategies_signed_mul { - ($TestName: ident, $Type: ty) => { + ($TestName: ident, $Type: ty, $Prec: literal) => { #[test] fn $TestName() { - let two_point_five: Num<$Type, 3> = Num::from_f32(2.5); - let five: Num<$Type, 3> = 5.into(); - let minus_two_point_five: Num<$Type, 3> = Num::from_f32(-2.5); - let minus_six_point_twenty_five: Num<$Type, 3> = Num::from_f32(6.25); + 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), @@ -1762,10 +1859,12 @@ mod tests { minus_six_point_twenty_five ); - let very_small: Num<$Type, 2> = (<$Type>::MIN + 8 >> 3).into(); - let very_small_times_minus_thirty_two: Num<$Type, 2> = (-32).into(); - let negative_very_small_squared: Num<$Type, 2> = (<$Type>::MAX >> 2).into(); - assert_eq!(very_small.checked_mul(-4), None); + 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), @@ -1775,7 +1874,7 @@ mod tests { very_small.overflowing_mul(-very_small), (negative_very_small_squared, true) ); - assert_eq!(very_small.saturating_mul(-4), Num::from_raw(<$Type>::MAX)); + 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) @@ -1793,7 +1892,7 @@ mod tests { } macro_rules! test_panic_on_overflow { - ($ModName: ident, $Type: ty) => { + ($ModName: ident, $Type: ty, $Prec: literal) => { mod $ModName { use super::*; @@ -1803,8 +1902,8 @@ mod tests { should_panic(expected = "attempt to add with overflow") )] fn add() { - let max_minus_two: Num<$Type, 3> = ((<$Type>::MAX >> 3) - 2).into(); - let three: Num<$Type, 3> = 3.into(); + let max_minus_two: Num<$Type, $Prec> = ((<$Type>::MAX >> $Prec) - 2).into(); + let three: Num<$Type, $Prec> = 3.into(); let _ = max_minus_two + three; } @@ -1822,8 +1921,8 @@ mod tests { should_panic(expected = "attempt to multiply with overflow") )] fn mul() { - let max_minus_two: Num<$Type, 3> = ((<$Type>::MAX >> 3) - 2).into(); - let two: Num<$Type, 3> = 2.into(); + let max_minus_two: Num<$Type, $Prec> = ((<$Type>::MAX >> $Prec) - 2).into(); + let two: Num<$Type, $Prec> = 2.into(); let _ = max_minus_two * two; } @@ -1833,8 +1932,8 @@ mod tests { should_panic(expected = "attempt to subtract with overflow") )] fn sub() { - let min_plus_two: Num<$Type, 3> = ((<$Type>::MIN >> 3) + 2).into(); - let three: Num<$Type, 3> = 3.into(); + let min_plus_two: Num<$Type, $Prec> = ((<$Type>::MIN >> $Prec) + 2).into(); + let three: Num<$Type, $Prec> = 3.into(); let _ = min_plus_two - three; } } @@ -1842,7 +1941,7 @@ mod tests { } macro_rules! test_panic_on_overflow_signed_mul { - ($ModName: ident, $Type: ty) => { + ($ModName: ident, $Type: ty, $Prec: literal) => { mod $ModName { use super::*; @@ -1852,7 +1951,8 @@ mod tests { should_panic(expected = "attempt to multiply with overflow") )] fn mul_negative_times_positive() { - let very_small: Num<$Type, 2> = (<$Type>::MIN + 8 >> 3).into(); + let very_small: Num<$Type, $Prec> = + (<$Type>::MIN + (2 << $Prec) >> $Prec).into(); let _ = very_small * -very_small; } @@ -1862,32 +1962,39 @@ mod tests { should_panic(expected = "attempt to multiply with overflow") )] fn mul_negative_times_negative() { - let very_small: Num<$Type, 2> = (<$Type>::MIN + 8 >> 3).into(); - let minus_thirty_two: Num<$Type, 2> = (-32).into(); + 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); - test_overflow_strategies!(test_u8, u8); - test_overflow_strategies!(test_i16, i16); - test_overflow_strategies!(test_u16, u16); - test_overflow_strategies!(test_i32, i32); - test_overflow_strategies!(test_u32, u32); - test_overflow_strategies_signed_mul!(test_i8_signed_mul, i8); - test_overflow_strategies_signed_mul!(test_i16_signed_mul, i16); - test_overflow_strategies_signed_mul!(test_i32_signed_mul, i32); - test_panic_on_overflow!(test_panic_i8, i8); - test_panic_on_overflow!(test_panic_u8, u8); - test_panic_on_overflow!(test_panic_i16, i16); - test_panic_on_overflow!(test_panic_u16, u16); - test_panic_on_overflow!(test_panic_i32, i32); - test_panic_on_overflow!(test_panic_u32, u32); - test_panic_on_overflow_signed_mul!(test_panic_i8_signed_mul, i8); - test_panic_on_overflow_signed_mul!(test_panic_i16_signed_mul, i16); - test_panic_on_overflow_signed_mul!(test_panic_i32_signed_mul, i32); + 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]