Fixed-point math in C#, part 2

Welcome to the second tutorial on fixed-point math in C#. In the last post, I covered the basics of fixed point arithmetic and why it's still useful to games even today.

In this part of the tutorial, I'm going to dive right into how to actually implement simple arithmetic for our custom Fixed type. In the end, we will be able add, subtract, multiply, and divide our custom Fixed type seamlessly just as we would any other numeric type.

Addition & Subtraction

To get started, we'll implement addition and subtraction, as these are the easiest. We'll use operator overloading to implement arithmetic for our type. As I mentioned in the first post, when dealing with fixed point we can just add the whole numbers together or subtract one from the other and just keep the decimal where it is - so, in fact, we don't need to do anything to our internal representation other than just adding or subtracting.

public static Fixed operator +(Fixed lhs, Fixed rhs)
{
    lhs.rawValue += rhs.rawValue;
    return lhs;
}

public static Fixed operator -(Fixed lhs, Fixed rhs)
{
    lhs.rawValue -= rhs.rawValue;
    return lhs;
}

All we need to do in this case is just overload the addition and subtraction operators and just add or subtract the raw integer values.

Multiplication & Division

As I mentioned before, when multiplying two fixed point numbers what you get back is a number that has the sum of their decimal places. When we multiply two Q16 numbers together, we get back a Q32 number. Therefore, we must shift away 16 decimal places afterwards in order to get a Q16 number.

public static Fixed operator *(Fixed lhs, Fixed rhs)
{
    lhs.rawValue *= rhs.rawValue;
    lhs.rawValue >>= 16; // convert the Q32 number back into Q16
    return lhs;
}

Division is almost as simple. In division the exact reverse of multiplication happens - that is, instead of ending up with the sum of the decimal places, we end up with the difference. In our case, dividing two Q16 numbers would actually leave us with a Q0 number - just a whole number! The problem is that we can't just shift afterwards, since we've already lost those decimal places. Instead, we're going to shift before, converting the lefthand operand into a Q32 number and then performing the division, leaving us with a Q16 number.

public static Fixed operator /(Fixed lhs, Fixed rhs)
{
    lhs.rawValue <<= 16; // convert into Q32 number
    lhs.rawValue /= rhs.rawValue; // is now a Q16 number again
    return lhs;
}

Conclusion

I know it's a fairly small post, but I hope you've found it informative anyway. Implementation of other numeric types is left as an excercise for the reader - for example, allowing support for multiplying by integers.

In the next post I'll be taking a look at rounding out the Fixed type with a custom math library, providing support for common math functions using Fixed numbers instead of floats.

Posted in Coding & Dev on Jan 24, 2017


blog comments powered by Disqus
Subscribe for updates
RSS
Categories