Поделиться через


MathExtensions

Fluent programming is cool, and with extension methods you can make your code much more elegant.  I’ve been programming for years, but I still have to squint when I see an expression like Math.Min(z, Math.Max(y, Math.Pow(x, Math.Sqrt(x)))).  Is z the lower limit and y the upper limit?  Or is it the other way around?  It’s much easier and more concise for me to read the same expression as x.Pow(x.Sqrt()).AtLeast(y).AtMost(z).  So for that reason I wrote a macro that went through all of the methods on the Math class and generated extension methods for each of them, so I’d never have to write Math. again.

You’ll also notice that I manually went in and renamed Math.Min and Math.Max to AtMost and AtLeast, respectively.  That’s my most common usage of Math.Min and Math.Max, and it doesn’t sound right to say x.Max(y) in any scenario I could think of.  If you’re really conceptually wanting to find the maximum of two values, I think it reads more clearly as Math.Max(x, y) instead of x.Max(y) anyway.

Another example of bloated code is when you check to see if a number is between two other numbers.  You can write if (y <= x && x <= z) if you know in advance that y is less than z, but if y and z could be swapped then you have to write if ((y <= x && x <= z) || (y >= x && x >= z)).  What a mess!  I’d rather write a subroutine that does the logic internally, but before extension methods I would have no where to put it.  This is why you commonly see codebases full of code like if (Utils.IsBetween(x, y, z)).  But I hate classes called Utils or Utilities or Helpers because they’re like a band-aid on my code.  They stick out like a sore thumb, they make it impossible to write elegant expressions, and I never know where in my project hierarchy to put the class file.  But now that I have a file full of math extension methods in System.More, I can add the utility method there as if it was in the BCL to begin with, and I can write if (x.IsBetween(y, z)) instead.

While I’m adding methods that should have been there in the first place, I’ve always been surprised that Math.Floor and Math.Ceiling only round to 0.0 and 1.0.  I’ve had plenty of scenarios when I need to round a number up to the nearest factor of 10, or down to the nearest factor of 100, etc.  I guess that’s why they’re called Floor and Ceiling instead of RoundDown and RoundUp.  Well, with extension methods, both of those problems are solved.

Another couple of nice extension methods around Double.NaN are the x.IsNaN() and x.GetValueOrDefault() methods.  IsNaN would have been perfect for an extension property, but unfortunately that feature was cut.  GetValueOrDefault mirrors the method by the same name on System.Nullable, including the overload that takes the fallback value as an argument.  Without a specific value, it defaults to 0.0.

The full MathExtensions extension class is attached.  Enjoy!

MathExtensions.cs