gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > DS fixed point math?

#41141 - rize - Mon Apr 25, 2005 10:28 pm

I'm wondering about the fixed point math and the math co processor. I will attempt to answer my own questions and if anyone knows that my answers are correct, please say so.

I see the typedef:

Code:
typedef int  f32;             // 1.19.12 fixed point for matricies
#define intof32(n)           ((n) << 12)
#define f32toint(n)          ((n) >> 12)
#define floatof32(n)         ((f32)((n) * (1 << 12)))
#define f32tofloat(n)        (((float)(n)) / (float)(1<<12))


Should I be using f32 instead of double and/or float at all times? I'm thinking yes unless I need more precision than 12 bits on the right side of the decimal.

I don't have a lot of C experience. When I do division using the symbol / is that always software division or is it designed to invoke the hardware division typedefs in math.h? I'm thinking that / always uses software division.

What's the best way to make an f32 constant? Should I just make a float constant and convert to f32? using floatof32(n)? I'm thinking yes.

(f32)1234.56 doesn't make a useful f32 constant does it? I think it just makes a float using the value 1234.56 and changes the type without multiplying by 2^12 (giving me some garbage value).

I see that intof32 simply shifts left 12 spots. Does this work for negative numbers (i.e. does the format 1.19.12 use two's compliment or is it assumed that no one needs negative f32 values)? I'm assuming that it does work because the fixed point type is really just a different way of interpretted an integer (that is shifting left by 12 before doing math and shifting back before using the integer value so that the 12 low bits are used to retain precision on the right side of the decimal.

What happens if I use / on two f32's? I'm thinking it will do a software integer divide (or else give me a type error) which will produce accurate results because the format works as I mentioned above. If it gives a type error, I assume that typecasting as int and then back to f32 will work.

#41149 - sajiimori - Mon Apr 25, 2005 11:37 pm

Quote:
Should I be using f32 instead of double and/or float at all times? I'm thinking yes unless I need more precision than 12 bits on the right side of the decimal.
If you need more than 12 bits of fraction, then just treat the value as if more bits are fractional. That's the trick behind fixed point: you pretend that some of the bits are fractional, even though C (and the CPU) just treat it like a regular integer.

Don't use floats or doubles at runtime. It's ok to use a floating point value if you're sure that the compiler will optimize it out. For instance:
Code:
int x = (int)(3.5 * 12.34);  // math is done at compile time

Quote:
When I do division using the symbol / is that always software division or is it designed to invoke the hardware division typedefs in math.h?
It'll use the compiler's default software divide.
Quote:
What's the best way to make an f32 constant? Should I just make a float constant and convert to f32? using floatof32(n)?
Code:
const fx32 value = floatof32(1.5);

Quote:
(f32)1234.56 doesn't make a useful f32 constant does it?
The fractional part of the float will be dropped giving the integer 1234. Then if you treat the low 12 bits of the integer as fractional then it is 1234/(1 << 12), or approximately 0.3.
Quote:
I see that intof32 simply shifts left 12 spots. Does this work for negative numbers (i.e. does the format 1.19.12 use two's compliment or is it assumed that no one needs negative f32 values)?
Yes.
Quote:
What happens if I use / on two f32's?
It will do a regular integer divide, which is not conceptually correct if the low 12 bits are supposed to be fractional.

I would suggest experimenting with fixed point values on your computer, so you can get a feel for how the math works out.

#41154 - rize - Tue Apr 26, 2005 12:14 am

Quote:
If you need more than 12 bits of fraction, then just treat the value as if more bits are fractional.


Yes I understand. However, the math coprocessor specifically requires the format 1.19.12 so it must be shifted back to that format before being used with the coprocessor I assume. I actually figured out how to use fixed point decimal on my own about two years ago without realizing what it was called. I figured it was a good trick that had already been figured out. Nice to see it pop up here!

Floats and doubles appear to work at run time, but they wouldn't be efficient so I will avoid using them except possibly for quick debug output and that sort of thing.

Quote:
[ dividing f32 by f32 using / ] will do a regular integer divide, which is not conceptually correct if the low 12 bits are supposed to be fractional.


Of course you're right. It's like doing 0.5 * 0.5 on paper. I get 25 but have to account for moving the decimal twice not once.

So + and - will work between f32's, but for * and / I need to shift the result right or left by twelve respectively (unless I use hardware divide in which case that is done for me).

I think that's everything. Thanks for clearing that last one up for me especially. That would have been a pain.

#41164 - rize - Tue Apr 26, 2005 1:54 am

Ok, I tried out the following four lines of code and only the first three work. The fourth, which should use the hardware to divide two f32's and produce an f32 result doesn't seem to work right.

Code:
ty = f32toint(  floatof32( (float)IPC->touchY/(float)17.508 )  ) -14;

ty = f32toint(  (intof32(IPC->touchY)/floatof32(17.508)) << 12  ) -14;

ty =             div32(intof32(IPC->touchY), floatof32(17.508) )  ) -14;

ty = f32toint(  divf32(intof32(IPC->touchY), floatof32(17.508) )  ) -14;

#41176 - rize - Tue Apr 26, 2005 2:36 am

After the = vs == thing earlier today I hate bringing this up but if divf32 takes two f32's and returns an f32 why is the DIV_CR being set to DIV_64_32?

And why is DIV_NUMERATOR64 being set to an f32 left shifted by 12?

Code:
///////////////////////////////////////
//  Fixed point divide
//  Takes 1.19.12 numerator and denominator
//  and returns 1.19.12 result
static inline f32 divf32(f32 num, f32 den)
{
   DIV_CR = DIV_64_32;
   
   while(DIV_CR & DIV_BUSY);

   DIV_NUMERATOR64 = ((int64)num) << 12;
   DIV_DENOMINATOR32 = den;

   while(DIV_CR & DIV_BUSY);

   return (DIV_RESULT32);
}


It would seem to me that the integer version below could do what the one above claims to do by simply shifting the result left by 12 before returning it:

Code:
///////////////////////////////////////
//  Interger divide
//  Takes a 32 bit numerator and 32 bit
//   denominator and returns 32 bit result
static inline int32 div32(int32 num, int32 den)
{
   DIV_CR = DIV_32_32;
   
   while(DIV_CR & DIV_BUSY);

   DIV_NUMERATOR32 = num;
   DIV_DENOMINATOR32 = den;

   while(DIV_CR & DIV_BUSY);

   return (DIV_RESULT32);
}

#41181 - sajiimori - Tue Apr 26, 2005 3:26 am

When multiplying or dividing two fx32 values it's good to use a 64 bit workspace to reduce the loss of precision. Note that if you simply do the divide and then shift the result left 12 bits, you've lost all fractional precision.

#41183 - rize - Tue Apr 26, 2005 3:32 am

True. Can't believe I missed that. It works in the case I'm using it for because I was going to int anyway (trying to get an accurate input point from the touch screen).

So why doesn't the first one (divf32) work for me? It doesn't seem to return a proper f32 value.

I'll try to test it with some values I'm certain are proper f32 variables.