Blog of Rob Galanakis (@robgalanakis)

Math Libraries

.NET 3d math libraries are a bit strange. There are a few options available- arguably the best one, XNA, only works for 32 bit. If you want an x64 math library, your pickings are slim. So, after having been through a number of the public offerings available, and having dabbled in rolling my own a couple times, here are some of my pet peeves, comments, questions, and desires.

Use double (System.Double), not float (System.Single)
The most common rebuttal to a lot of my arguments is going to be “performance” (including memory), but I really, really think any native .NET math library should use double by default. floats (aka, System.Single) are only beneficial from a memory standpoint, despite what some may say- processors actually calculate at a higher precision and truncate down to fit the desired memory. I’d give you a reference but that statement is only from the word of some very reputable programmers, so you can look it up yourself if you disagree. double is the default C (and C#) floating point number type, and I feel it should be in any standard library (it already is in System.Math). The other thing to consider is the customer. Is this a developer and tool-facing library? Or will this be used in game runtimes? Either way, I’d rather start with a good, robust, reusable library, than one that is generic-tuned for better performance but you will probably end up tweaking it for performance more anyway. So, use doubles instead of floats, and you can optimize it later.

Use properties, not fields
A real pet peeve, which doesn’t have performance implications, is using public fields instead of public properties. This is class design 101 and I don’t believe it needs explanation, but I see it over and over. Use properties, not fields.

Methods without side effects
Don’t give me a ‘Normalize()’ method that will mutate my vector. Give me a ‘Normalized()’ method that will return ‘this’ vector normalized. Actually this is one instance of a much bigger issue:

Math classes should be immutable!
Well this is a common one to not think about. Let me ask, if you do something like:

double d1 = 5.0d;
double d2 = d1;
d1 += 1; //d2 is still equal to 5.0d!

are you changing the value of ‘d1’ or are you changing the value of ‘5.0’? Well, neither exactly, but you are certainly not changing the value of ‘5.0’, and you are not changing the value of ‘d2’. d2 refers to an instance of a System.Double that has and forever will have a value of 5.0d. You can change what value d2 refers to, but you cannot change the value of the value. That said, why do we create libraries where this is legal?

Vector3 v1 = new Vector3();
Vector3 v2 = v1;
v1 += 1.0d; //v2's value has changed!

What I’d much rather see is fully-immutable classes, so that we have:

Vector3 v1 = new Vector3();
Vector3 v2 = v1;
v1 += 1.0d; //compiler error!
v1 = v1 + 1.0d; //this works, and v2 still has the same value

Using immutable classes also fixes problems like:

Use constructors, not mutation
I hate stuff like

Vector3 v;
v.X = 1;
v.Y = 2.5;
v.Z = 3.0;
return v;

rather than

return new Vector3(1, 2.5, 3.0);

If you use immutable classes, the former would be impossible, so problem solved.

Duplicated code
This, along with float/double, is the other performance consideration. Apparently the JIT may or may not inline some method calls, so duplicating code inside a method may be faster. A balance must be struck- duplicate as little code as possible, while making the code as easily understandable and refactorable as possible. I have written crazy ways to get full code reuse, that ended up being somewhat slower, but much more difficult to follow, so I abandoned it (though I may show the idea in a future post). So, in order to walk this line well:

Three (or more) different ways to do the same thing
There’s no reason to give give “public static Vector3 Add(Vector3 v1, Vector3 v2)”, “public Vector3 Add(Vector3 other)”, and “public static Vector3 operator +(Vector3 v1, Vector3 v2)” (and immutability means you’d never want “public void Vector3 Add(Vector3 other)”, right?). Give me two at most- the static ‘Add’ method is unnecessary and the worst way to call the method. If you are going to reuse code extensively, it is more acceptable, but if you’re re-writing code, all those extra lines will add up when making changes or refactoring.

Make sure it works
In our internal EA math library, I saw the following two methods:

public override int GetHashCode()
{
    return this.X.GetHashCode() + this.Y.GetHashCode();
}
public override bool Equals(Vector2 other)
{
    return other.X == this.X && other.Y == this.Y;
}

I can’t believe this requires a comment, quite honestly. The GetHashCode implementation is a joke- you are guaranteed to get any number of objects with the same hash code, and the distribution of those objects will all clump. Like, say, the vectors (10, -10) and (3, -3) and (0,0). The Equals comparison will obviously fail because comparing floating point binary types (doubles and floats) like that is never a good idea due to precision and general float math conundrums.

Use conversions
This is mostly personal preference. I’d rather have conversion operators to convert between Quaternion and Matrix, than ‘Quaternion.FromMatrix’ and ‘Matrix.FromQuaternion’ static methods. Of course, they should almost always be explicit, since implicit operators don’t make sense when going between different types of numbers like most math libraries have.

Final Comments
Two closing remarks. First, I’d suggest using F# if you are considering writing a custom math library. The performance of F# is blazing fast, and you’ll get a lot of these requests built in. Second, I understand there are things I overlooked and/or am no expert on. The focus of this is making an easily usable and maintainable library, something I think I do relatively well with; NOT a highly-performant library, which is something I’m less versed in and requires a deep understanding of the clr, what IL code your higher-level code compiles into, and other low(er) level issues.

Leave a Reply