As a followup to my article on Inlining Math.ceil(), I decided to inline some more functions in the Math class. Read on for the code as well as tests proving correctness and speed.

First of all, I didn’t make inlined versions of any of the more complex functions in Math: acos, asin, atan, atan2, cos, exp, log, pow, random, round, sin, sqrt, or tan. It seems unlikely that an AS3 solution could beat these as the non-trivial amount of AS3 to implement them would likely perform slower than their native code equivalents. With that said, I did make AS3 versions of abs, ceil, floor, max, and min. Additionally, I made a pair of extra functions– max2 and min2– because var args is so slow. Here they are along with the correctness and speed tests:

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	/**
	*   An app with inlined versions of functions from the Math class as well
	*   as tests proving their correctness and speed.
	*   @author Jackson Dunstan
	*/
	public class InlinedMathTest extends Sprite
	{
		public function InlinedMathTest()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			var logger:TextField = new TextField();
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
			function log(msg:*): void { logger.appendText(msg + "\n"); }
 
 			var tests:Array = [
 				["abs",
 					[-2.1],
 					[-2],
 					[0],
 					[2],
 					[2.1],
 					[NaN]
 				],
 				["ceil",
 					[-2.1],
 					[-2],
 					[0],
 					[2],
 					[2.1],
 					[NaN]
 				],
 				["floor",
 					[-2.1],
 					[-2],
 					[0],
 					[2],
 					[2.1],
 					[NaN]
 				],
 				["max",
 					[-2,2],
 					[2,-1],
 					[2,2],
 					[0,0],
 					[NaN,-1],
 					[-1,NaN],
 					[NaN,NaN],
 					[2,3],
 					[-2,-3],
 					[2,3,4,5,6],
 					[6,5,4,3,2],
 					[1,2,3,NaN,4],
 					[1,2,3,4,NaN]
 				],
 				["min",
 					[-2,2],
 					[2,-1],
 					[2,2],
 					[0,0],
 					[NaN,-1],
 					[-1,NaN],
 					[NaN,NaN],
 					[2,3],
 					[-2,-3],
 					[2,3,4,5,6],
 					[6,5,4,3,2],
 					[1,2,3,NaN,4],
 					[1,2,3,4,NaN]
 				]
 			];
 
 			for each (var test:Array in tests)
 			{
 				var testName:String = test.shift();
				log(testName + ":");
				for each (var values:Array in test)
				{
					var resI:Number = this[testName].apply(this, values);
					var resM:Number = Math[testName].apply(Math, values);
					var same:Boolean = isNaN(resI) && isNaN(resM) || resI == resM;
					log("\t" + (same ? "PASS" : "FAIL") + ". Inline returned: " + resI + ". Math returned: " + resM + ". Input: " + values);
				}
			}
 
			const NUM_ITERATIONS:int = 1000000;
			var i:int;
			var beforeTime:int;
			var result:Number;
			var val:Number = 1;
			var val1:Number = 1;
			var val2:Number = 2;
			var val3:Number = 3;
			var val4:Number = 4;
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = abs(val);
			}
			log("abs (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.abs(val);
			}
			log("abs (Math): " + (getTimer()-beforeTime));
			log("");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = ceil(val);
			}
			log("ceil (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.ceil(val);
			}
			log("ceil (Math): " + (getTimer()-beforeTime));
			log("");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = floor(val)
			}
			log("floor (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.floor(val);
			}
			log("floor (Math): " + (getTimer()-beforeTime));
			log("");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = max(val1, val2, val3, val4);
			}
			log("max (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.max(val1, val2, val3, val4);
			}
			log("max (Math): " + (getTimer()-beforeTime));
			log("");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = min(val1, val2, val3, val4);
			}
			log("min (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.min(val1, val2, val3, val4);
			}
			log("min (Math): " + (getTimer()-beforeTime));
			log("");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = max2(val1, val2);
			}
			log("max2 (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.max(val1, val2);
			}
			log("max2 (Math): " + (getTimer()-beforeTime));
			log("");
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = min2(val1, val2);
			}
			log("min2 (inline): " + (getTimer()-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < NUM_ITERATIONS; ++i)
			{
				result = Math.min(val1, val2);
			}
			log("min2 (Math): " + (getTimer()-beforeTime));
			log("");
		}
 
		public function abs(val:Number): Number
		{
			return val < 0 ? -val : val;
		}
 
		public function ceil(val:Number): Number
		{
			return (!(val <= 0) && !(val > 0)) ? NaN : val == int(val) ? val : val >= 0 ? int(val+1) : int(val);
		}
 
		public function floor(val:Number): Number
		{
			return (!(val <= 0) && !(val > 0)) ? NaN : val == int(val) ? val : val < 0 ? int(val-1) : int(val);
		}
 
		public function max(val1:Number, val2:Number, ...rest): Number
		{
			if ((!(val1 <= 0) && !(val1 > 0)) || (!(val2 <= 0) && !(val2 > 0)))
			{
				return NaN;
			}
			var ret:Number = val1 > val2 ? val1 : val2;
			var numRest:int = rest.length;
			var cur:Number;
			for (var i:int = 0; i < numRest; ++i)
			{
				cur = rest[i];
				if ((!(cur <= 0) && !(cur > 0)))
				{
					return NaN;
				}
				if (cur > ret)
				{
					ret = cur;
				}
			}
			return ret;
		}
 
		public function min(val1:Number, val2:Number, ...rest): Number
		{
			if ((!(val1 <= 0) && !(val1 > 0)) || (!(val2 <= 0) && !(val2 > 0)))
			{
				return NaN;
			}
			var ret:Number = val1 < val2 ? val1 : val2;
			var numRest:int = rest.length;
			var cur:Number;
			for (var i:int = 0; i < numRest; ++i)
			{
				cur = rest[i];
				if ((!(cur <= 0) && !(cur > 0)))
				{
					return NaN;
				}
				if (cur < ret)
				{
					ret = cur;
				}
			}
			return ret;
		}
 
		public function max2(val1:Number, val2:Number): Number
		{
			if ((!(val1 <= 0) && !(val1 > 0)) || (!(val2 <= 0) && !(val2 > 0)))
			{
				return NaN;
			}
			return val1 > val2 ? val1 : val2;
		}
 
		public function min2(val1:Number, val2:Number): Number
		{
			if ((!(val1 <= 0) && !(val1 > 0)) || (!(val2 <= 0) && !(val2 > 0)))
			{
				return NaN;
			}
			return val1 < val2 ? val1 : val2;
		}
	}
}

If the TextField output grows beyond where you can see all the text, simply right-click it, choose “Select All”, and copy/paste the output into a text editor. All of the correctness tests pass, so I won’t bother to post a long list of “PASS” lines. The speed tests are quite interesting though:

Function 2.2 Ghz Intel Core 2 Duo, 2 GB RAM, Mac OS X 10.6
abs 22 inline, 65 Math
ceil 24 inline, 72 Math
floor 24 inline, 69 Math
max 759 inline, 212 Math
min 750 inline, 215 Math
max2 23 inline, 75 Math
min2 21 inline, 69 Math

You may notice a curious bit of code repeated in many of the functions above:

(!(val <= 0) && !(val > 0))

For details on this, see Wednesday’s article on how to make a Faster isNaN() function. Also, make sure to note that the inline version of max and min are actually a good deal slower than their equivalents in Math. Do not accidentally use these in a fit of inlining everything! Instead, use the more common min2 and max2 and then simply use Math.min and Math.max where you need to pass more than two parameters.

Lastly, it is certainly possible to get even more speed out of these “inlined” functions by truly inlining them. You may have to do this manually by essentially replacing the function call with the body of the function itself. This will save you the function call time and cost you readability, maintainability, and all the other advantages that drive us to create functions in the first place. But if you really need every last drop of performance, by all means go for it.