diff --git a/book/src/00-introduction/05-help_and_resources.md b/book/src/00-introduction/05-help_and_resources.md index 63a646f..59a51f5 100644 --- a/book/src/00-introduction/05-help_and_resources.md +++ b/book/src/00-introduction/05-help_and_resources.md @@ -32,6 +32,10 @@ Reference](https://doc.rust-lang.org/nightly/reference/introduction.html) to see if they cover it. You can mostly ignore that big scary red banner at the top, things are a lot better documented than they make it sound. +If you need help trying to fiddle your math down as hard as you can, there are +resources such as the [Bit Twiddling +Hacks](https://graphics.stanford.edu/~seander/bithacks.html) page. + As to GBA related lore, Ketsuban and I didn't magically learn this all from nowhere, we read various technical manuals and guides ourselves and then distilled those works oriented around C and C++ into a book for Rust. diff --git a/book/src/01-quirks/02-fixed_only.md b/book/src/01-quirks/02-fixed_only.md index c2c5c95..bb0e401 100644 --- a/book/src/01-quirks/02-fixed_only.md +++ b/book/src/01-quirks/02-fixed_only.md @@ -166,14 +166,6 @@ can just use a macro and invoke it once per type. Guess what we're gonna do. macro_rules! fixed_point_methods { ($t:ident) => { impl Fx<$t, F> { - /// Gives 0 for this type. - pub fn zero() -> Self { - Fx { - num: 0, - phantom: PhantomData, - } - } - /// Gives the smallest positive non-zero value. pub fn precision() -> Self { Fx { @@ -185,7 +177,7 @@ macro_rules! fixed_point_methods { /// Makes a value with the integer part shifted into place. pub fn from_int_part(i: $t) -> Self { Fx { - num: i << F::to_u8(), + num: i << F::U8, phantom: PhantomData, } } @@ -201,28 +193,34 @@ fixed_point_methods! {i32} fixed_point_methods! {u32} ``` -Now _you'd think_ that those can all be `const`, but at the moment you can't -have a `const` function with a bound on any trait other than `Sized`, so they -have to be normal functions. +Now _you'd think_ that those can be `const`, but at the moment you can't have a +`const` function with a bound on any trait other than `Sized`, so they have to +be normal functions. Also, we're doing something a little interesting there with `from_int_part`. We -can take our `F` type and get it as a value instead of a type using `to_u8`. +can take our `F` type and get its constant value. There's other associated +constants if we want it in other types, and also non-const methods if you wanted +that for some reason (maybe passing it as a closure function? dunno). -## Casting Values +## Casting Base Values -Next, once we have a value in one type, we need to be able to move it into -another type. A particular `Fx` type is a base number type and a fractional -count, so there's two ways we might want to move it. +Next, once we have a value in one base type we will need to be able to move it +into another base type. Unfortunately this means we gotta use the `as` operator, +which requires a concrete source type and a concrete destination type. There's +no easy way for us to make it generic here. -For casting the base type it's a little weird. Because there's so many number -types, and we can't be generic about them when using `as`, we'd have to make -like 30 functions (6 base number types we're using, times 5 target number types -you could cast to). Instead, we'll write it just once, and let the user pass a -closure that does the cast. +We could let the user use `into_raw`, cast, and then do `from_raw`, but that's +error prone because they might change the fractional bit count accidentally. +This means that we have to write a function that does the casting while +perfectly preserving the fractional bit quantity. If we wrote one function for +each conversion it'd be like 30 different possible casts (6 base types that we +support, and then 5 possible target types). Instead, we'll write it just once in +a way that takes a closure, and let the user pass a closure that does the cast. +The compiler should merge it all together quite nicely for us once optimizations +kick in. -We can put this as part of the basic impl block that `from_raw` and `into_raw` -are part of. If can avoid having code inside a macro we'll do it just because -macros are messy. +This code goes outside the macro. I want to avoid too much code in the macro if +we can, it's a little easier to cope with I think. ```rust /// Casts the base type, keeping the fractional bit quantity the same. @@ -234,25 +232,317 @@ macros are messy. } ``` -It's... not the best to have to pass in the casting operation like that. -Hopefully we won't have to use it much. +It's horrible and ugly, but Rust is just bad at numbers sometimes. -Also we might want to change the amount of fractional bits in a number. Oh, -gosh, this one is kinda complicated. +## Adjusting Fractional Part -## Addition / Subtraction +In addition to the base value we might want to change our fractional bit +quantity. This is actually easier that it sounds, but it also requires us to be +tricky with the generics. We can actually use some typenum type level operators +here. -## Multiplication / Division +This code goes inside the macro: we need to be able to use the left shift and +right shift, which is easiest when we just use the macro's `$t` as our type. We +could alternately put a similar function outside the macro and be generic on `T` +having the left and right shift operators by using a `where` clause. As much as +I'd like to avoid too much code being generated by macro, I'd _even more_ like +to avoid generic code with huge and complicated trait bounds. It comes down to +style, and you gotta decide for yourself. + +```rust + /// Changes the fractional bit quantity, keeping the base type the same. + pub fn adjust_fractional_bits>(self) -> Fx<$t, Y> { + let leftward_movement: i32 = Y::to_i32() - F::to_i32(); + Fx { + num: if leftward_movement > 0 { + self.num << leftward_movement + } else { + self.num >> (-leftward_movement) + }, + phantom: PhantomData, + } + } +``` + +There's a few things at work. First, we introduce `Y` as the target number of +fractional bits, and we _also_ limit it that the target bits quantity can't be +the same as we already have using a type-level operator. If it's the same as we +started with, why are you doing the cast at all? + +Now, once we're sure that the current bits and target bits aren't the same, we +compute `target - start`, and call this our "leftward movement". Example: if +we're targeting 8 bits and we're at 4 bits, we do 8-4 and get +4 as our leftward +movement. If the leftward_movement is positive we naturally shift our current +value to the left. If it's not positive then it _must_ be negative because we +eliminated 0 as a possibility using the type-level operator, so we shift to the +right by the negative value. + +## Addition, Subtraction, Shifting, Negative, Comparisons + +From here on we're getting help from [this blog +post](https://spin.atomicobject.com/2012/03/15/simple-fixed-point-math/) by [Job +Vranish](https://spin.atomicobject.com/author/vranish/), so thank them if you +learn something. + +I might have given away the game a bit with those `derive` traits on our fixed +point type. For a fair number of operations you can use the normal form of the +op on the inner bits as long as the fractional parts have the same quantity. +This includes equality and ordering (which we derived) as well as addition, +subtraction, and bit shifting (which we need to do ourselves). + +This code can go outside the macro, with sufficient trait bounds. + +```rust +impl, F: Unsigned> Add for Fx { + type Output = Self; + fn add(self, rhs: Fx) -> Self::Output { + Fx { + num: self.num + rhs.num, + phantom: PhantomData, + } + } +} +``` + +The bound on `T` makes it so that `Fx` can be added any time that `T` can +be added to its own type with itself as the output. We can use the exact same +pattern for `Sub`, `Shl`, `Shr`, and `Neg`. With enough trait bounds, we can do +anything! + +```rust +impl, F: Unsigned> Sub for Fx { + type Output = Self; + fn sub(self, rhs: Fx) -> Self::Output { + Fx { + num: self.num - rhs.num, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Shl for Fx { + type Output = Self; + fn shl(self, rhs: u32) -> Self::Output { + Fx { + num: self.num << rhs, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Shr for Fx { + type Output = Self; + fn shr(self, rhs: u32) -> Self::Output { + Fx { + num: self.num >> rhs, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Neg for Fx { + type Output = Self; + fn neg(self) -> Self::Output { + Fx { + num: -self.num, + phantom: PhantomData, + } + } +} +``` + +Unfortunately, for `Shl` and `Shr` to have as much coverage on our type as it +does on the base type (allowing just about any right hand side) we'd have to do +another macro, but I think just `u32` is fine. We can always add more later if +we need. + +We could also implement `BitAnd`, `BitOr`, `BitXor`, and `Not`, but they don't +seem relevent to our fixed point math use, and this section is getting long +already. Just use the same general patterns if you want to add it in your own +programs. Shockingly, `Rem` also works directly if you want it, though I don't +forsee us needing floating point remainder. Also, the GBA can't do hardware +division or remainder, and we'll have to work around that below when we +implement `Div` (which maybe we don't need, but it's complex enough I should +show it instead of letting people guess). + +**Note:** In addition to the various `Op` traits, there's also `OpAssign` +variants. Each `OpAssign` is the same as `Op`, but takes `&mut self` instead of +`self` and then modifies in place instead of producing a fresh value. In other +words, if you want both `+` and `+=` you'll need to do the `AddAssign` trait +too. It's not the worst thing to just write `a = a+b`, so I won't bother with +showing all that here. It's pretty easy to figure out for yourself if you want. + +## Multiplication + +This is where things get more interesting. When we have two numbers `A` and `B` +they really stand for `(a*f)` and `(b*f)`. If we write `A*B` then we're really +writing `(a*f)*(b*f)`, which can be rewritten as `(a*b)*2f`, and now it's +obvious that we have one more `f` than we wanted to have. We have to do the +multiply of the inner value and then divide out the `f`. We divide by `1 << +bit_count`, so if we have 8 fractional bits we'll divide by 256. + +The catch is that, when we do the multiply we're _extremely_ likely to overflow +our base type with that multiplication step. Then we do that divide, and now our +result is basically nonsense. We can avoid this to some extent by casting up to +a higher bit type, doing the multiplication and division at higher precision, +and then casting back down. We want as much precision as possible without being +too inefficient, so we'll always cast up to 32-bit (on a 64-bit machine you'd +cast up to 64-bit instead). + +Naturally, any signed value has to be cast up to `i32` and any unsigned value +has to be cast up to `u32`, so we'll have to handle those separately. + +Also, instead of doing an _actual_ divide we can right-shift by the correct +number of bits to achieve the same effect. _Except_ when we have a signed value +that's negative, because actual division truncates towards zero and +right-shifting truncates towards negative infinity. We can get around _this_ by +flipping the sign, doing the shift, and flipping the sign again (which sounds +silly but it's so much faster than doing an actual division). + +Also, again signed values can be annoying, because if the value _just happens_ +to be `i32::MIN` then when you negate it you'll have... _still_ a negative +value. I'm not 100% on this, but I think the correct thing to do at that point +is to give `$t::MIN` as out output num value. + +Did you get all that? Good, because this is involves casting, we will need to +implement it three times, which calls for another macro. + +```rust +macro_rules! fixed_point_signed_multiply { + ($t:ident) => { + impl Mul for Fx<$t, F> { + type Output = Self; + fn mul(self, rhs: Fx<$t, F>) -> Self::Output { + let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32); + if pre_shift < 0 { + if pre_shift == core::i32::MIN { + Fx { + num: core::$t::MIN, + phantom: PhantomData, + } + } else { + Fx { + num: (-((-pre_shift) >> F::U8)) as $t, + phantom: PhantomData, + } + } + } else { + Fx { + num: (pre_shift >> F::U8) as $t, + phantom: PhantomData, + } + } + } + } + }; +} + +fixed_point_signed_multiply! {i8} +fixed_point_signed_multiply! {i16} +fixed_point_signed_multiply! {i32} + +macro_rules! fixed_point_unsigned_multiply { + ($t:ident) => { + impl Mul for Fx<$t, F> { + type Output = Self; + fn mul(self, rhs: Fx<$t, F>) -> Self::Output { + Fx { + num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t, + phantom: PhantomData, + } + } + } + }; +} + +fixed_point_unsigned_multiply! {u8} +fixed_point_unsigned_multiply! {u16} +fixed_point_unsigned_multiply! {u32} +``` + +## Division + +Division is similar to multiplication, but reversed. Which makes sense. This +time `A/B` gives `(a*f)/(b*f)` which is `a/b`, one _less_ `f` than we were +after. + +As with the multiplication version of things, we have to up-cast our inner value +as much a we can before doing the math, to allow for the most precision +possible. + +The snag here is that the GBA has no division or remainder. Instead, the GBA has +a BIOS function you can call to do `i32/i32` division. + +This is a potential problem for us though. If we have some unsigned value, we +need it to fit within the positive space of an `i32` _after the multiply_ so +that we can cast it to `i32`, call the BIOS function that only works on `i32` +values, and cast it back to its actual type. + +* If you have a u8 you're always okay, even with 8 floating bits. +* If you have a u16 you're okay even with a maximum value up to 15 floating + bits, but having a maximum value and 16 floating bits makes it break. +* If you have a u32 you're probably going to be in trouble all the time. + +So... ugh, there's not much we can do about this. For now we'll just have to +suffer some. + +// TODO: find a numerics book that tells us how to do `u32/u32` divisions. + +```rust +macro_rules! fixed_point_signed_division { + ($t:ident) => { + impl Div for Fx<$t, F> { + type Output = Self; + fn div(self, rhs: Fx<$t, F>) -> Self::Output { + let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8); + let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32); + Fx { + num: divide_result as $t, + phantom: PhantomData, + } + } + } + }; +} + +fixed_point_signed_division! {i8} +fixed_point_signed_division! {i16} +fixed_point_signed_division! {i32} + +macro_rules! fixed_point_unsigned_division { + ($t:ident) => { + impl Div for Fx<$t, F> { + type Output = Self; + fn div(self, rhs: Fx<$t, F>) -> Self::Output { + let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8); + let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32); + Fx { + num: divide_result as $t, + phantom: PhantomData, + } + } + } + }; +} + +fixed_point_unsigned_division! {u8} +fixed_point_unsigned_division! {u16} +fixed_point_unsigned_division! {u32} +``` ## Trigonometry +TODO: look up tables! arcbits! + ## Just Using A Crate -If you feel too intimidated by all of this then I'll suggest to you that the -[fixed](https://crates.io/crates/fixed) crate seems to be the best crate -available for fixed point math. +If, after seeing all that, and seeing that I still didn't even cover every +possible trait impl that you might want for all the possible types... if after +all that you feel too intimidated, then I'll cave a bit on your behalf and +suggest to you that the [fixed](https://crates.io/crates/fixed) crate seems to +be the best crate available for fixed point math. _I have not tested its use on the GBA myself_. It's just my recommendation from looking at the docs of the various options -available. +available, if you really wanted to just have a crate for it. diff --git a/src/bios.rs b/src/bios.rs index c2ac39c..f24b6f4 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -8,6 +8,11 @@ //! whatever value is necessary for that function). Some functions also perform //! necessary checks to save you from yourself, such as not dividing by zero. +//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The +//functions that never return must panic, the functions that return nothing +//should just do so, and the math functions should just return the correct math +//I guess. + /// (`swi 0x00`) SoftReset the device. /// /// This function does not ever return. @@ -175,17 +180,21 @@ pub fn vblank_interrupt_wait() { #[inline(always)] pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) { assert!(denominator != 0); - let div_out: i32; - let rem_out: i32; - unsafe { - asm!(/* ASM */ "swi 0x06" - :/* OUT */ "={r0}"(div_out), "={r1}"(rem_out) - :/* INP */ "{r0}"(numerator), "{r1}"(denominator) - :/* CLO */ "r3" - :/* OPT */ - ); + if cfg!(test) { + (numerator / denominator, numerator % denominator) + } else { + let div_out: i32; + let rem_out: i32; + unsafe { + asm!(/* ASM */ "swi 0x06" + :/* OUT */ "={r0}"(div_out), "={r1}"(rem_out) + :/* INP */ "{r0}"(numerator), "{r1}"(denominator) + :/* CLO */ "r3" + :/* OPT */ + ); + } + (div_out, rem_out) } - (div_out, rem_out) } /// As `div_rem`, keeping only the `div` output. diff --git a/src/fixed.rs b/src/fixed.rs index 98879c1..da8b596 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -2,8 +2,11 @@ //! Module for fixed point math types and operations. -use core::{convert::From, marker::PhantomData}; -use typenum::{marker_traits::Unsigned, U8}; +use core::{ + marker::PhantomData, + ops::{Add, Div, Mul, Neg, Shl, Shr, Sub}, +}; +use typenum::{consts::False, marker_traits::Unsigned, type_operators::IsEqual, U8}; /// Fixed point `T` value with `F` fractional bits. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] @@ -21,6 +24,7 @@ impl Fx { phantom: PhantomData, } } + /// Unwraps the inner value. pub fn into_raw(self) -> T { self.num @@ -35,17 +39,59 @@ impl Fx { } } +impl, F: Unsigned> Add for Fx { + type Output = Self; + fn add(self, rhs: Fx) -> Self::Output { + Fx { + num: self.num + rhs.num, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Sub for Fx { + type Output = Self; + fn sub(self, rhs: Fx) -> Self::Output { + Fx { + num: self.num - rhs.num, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Shl for Fx { + type Output = Self; + fn shl(self, rhs: u32) -> Self::Output { + Fx { + num: self.num << rhs, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Shr for Fx { + type Output = Self; + fn shr(self, rhs: u32) -> Self::Output { + Fx { + num: self.num >> rhs, + phantom: PhantomData, + } + } +} + +impl, F: Unsigned> Neg for Fx { + type Output = Self; + fn neg(self) -> Self::Output { + Fx { + num: -self.num, + phantom: PhantomData, + } + } +} + macro_rules! fixed_point_methods { ($t:ident) => { impl Fx<$t, F> { - /// Gives 0 for this type. - pub fn zero() -> Self { - Fx { - num: 0, - phantom: PhantomData, - } - } - /// Gives the smallest positive non-zero value. pub fn precision() -> Self { Fx { @@ -57,19 +103,22 @@ macro_rules! fixed_point_methods { /// Makes a value with the integer part shifted into place. pub fn from_int_part(i: $t) -> Self { Fx { - num: i << F::to_u8(), + num: i << F::U8, phantom: PhantomData, } } - /// Gives the raw inner value. - pub fn into_inner(&self) -> $t { - self.num - } - /// Changes the fractional bit quantity, keeping the base type the same. - pub fn change_bit_quantity(&self) -> Fx<$t, N> { - unimplemented!() + pub fn adjust_fractional_bits>(self) -> Fx<$t, Y> { + let leftward_movement: i32 = Y::to_i32() - F::to_i32(); + Fx { + num: if leftward_movement > 0 { + self.num << leftward_movement + } else { + self.num >> (-leftward_movement) + }, + phantom: PhantomData, + } } } }; @@ -82,5 +131,96 @@ fixed_point_methods! {u16} fixed_point_methods! {i32} fixed_point_methods! {u32} +macro_rules! fixed_point_signed_multiply { + ($t:ident) => { + impl Mul for Fx<$t, F> { + type Output = Self; + fn mul(self, rhs: Fx<$t, F>) -> Self::Output { + let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32); + if pre_shift < 0 { + if pre_shift == core::i32::MIN { + Fx { + num: core::$t::MIN, + phantom: PhantomData, + } + } else { + Fx { + num: (-((-pre_shift) >> F::U8)) as $t, + phantom: PhantomData, + } + } + } else { + Fx { + num: (pre_shift >> F::U8) as $t, + phantom: PhantomData, + } + } + } + } + }; +} + +fixed_point_signed_multiply! {i8} +fixed_point_signed_multiply! {i16} +fixed_point_signed_multiply! {i32} + +macro_rules! fixed_point_unsigned_multiply { + ($t:ident) => { + impl Mul for Fx<$t, F> { + type Output = Self; + fn mul(self, rhs: Fx<$t, F>) -> Self::Output { + Fx { + num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t, + phantom: PhantomData, + } + } + } + }; +} + +fixed_point_unsigned_multiply! {u8} +fixed_point_unsigned_multiply! {u16} +fixed_point_unsigned_multiply! {u32} + +macro_rules! fixed_point_signed_division { + ($t:ident) => { + impl Div for Fx<$t, F> { + type Output = Self; + fn div(self, rhs: Fx<$t, F>) -> Self::Output { + let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8); + let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32); + Fx { + num: divide_result as $t, + phantom: PhantomData, + } + } + } + }; +} + +fixed_point_signed_division! {i8} +fixed_point_signed_division! {i16} +fixed_point_signed_division! {i32} + +macro_rules! fixed_point_unsigned_division { + ($t:ident) => { + impl Div for Fx<$t, F> { + type Output = Self; + fn div(self, rhs: Fx<$t, F>) -> Self::Output { + let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8); + let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32); + Fx { + num: divide_result as $t, + phantom: PhantomData, + } + } + } + }; +} + +fixed_point_unsigned_division! {u8} +fixed_point_unsigned_division! {u16} +fixed_point_unsigned_division! {u32} + /// Alias for an `i16` fixed point value with 8 fractional bits. pub type fx8_8 = Fx; diff --git a/src/lib.rs b/src/lib.rs index c82a89b..c172301 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(not(test), no_std)] -#![cfg_attr(not(test), feature(asm))] +#![no_std] +#![feature(asm)] #![warn(missing_docs)] #![allow(clippy::cast_lossless)] #![deny(clippy::float_arithmetic)] @@ -59,7 +59,6 @@ pub mod builtins; pub mod fixed; -#[cfg(not(test))] pub mod bios; pub mod core_extras; diff --git a/todo_check.bat b/todo_check.bat new file mode 100644 index 0000000..6f1e3ea --- /dev/null +++ b/todo_check.bat @@ -0,0 +1,12 @@ +@echo off + +echo ------- +echo ------- + +set Wildcard=*.rs + +echo TODOS FOUND: +findstr -s -n -i -l "TODO" %Wildcard% + +echo ------- +echo -------