In a previous post, I explored ways to accomplish Math.min and max for integers with some bit twiddling
(http://guihaire.com/code/?p=549),
Today, we are going to explore ways to find the min or the max of 2 Numbers without using the infamous Math class.
Lets start with the min.
So the basic:
min = Math.min(i,j);
One way is to use the ternary operator ( cond ? a : b)
min = i<j ? i :j;
We could also use an interpolation method with the interpolant being a value = 0 when i>=j and 1 when i<j:
var m01:Number = Number(i<j); min = i* m01 + j*(1-m01);
We can rewritte it:
min = (i-j) * Number(i<j) + j;
Lets look at the generated code:
+1|-0 GetLocal(1)
+1|-0 GetLocal(2)
+1|-2 Subtract()
+1|-0 FindPropStrict(AbcQName(‘Number,AbcNamespace(22,’)))
+1|-0 GetLocal(1)
+1|-0 GetLocal(2)
+1|-2 LessThan()
+1|-2 CallProperty(AbcQName(‘Number,AbcNamespace(22,’)),1)
+1|-2 Multiply()
+1|-0 GetLocal(2)
+1|-2 Add()
+1|-1 ConvertDouble()
+0|-1 SetLocal(5)
Okay, lets try something a bit more compact without the CallProperty calls:
__asm( GetLocal(i), GetLocal(j), Subtract, GetLocal(j), GetLocal(i), LessThan, ConvertDouble, Multiply, GetLocal(j), Add, SetLocal(max) );
Similar code can be done for the Math.max (see at the end)
results!
here the full code:
private function minmaxFloatTest():void { var i:Number = 0; var j:Number = 0; var a:Number; var b:Number; var min:Number; var max:Number; var m01:Number; var iint:int = 0; var jint:int = 0; const REPS:Number = 100; const INC:Number = 0.1; var time1:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { min = Math.min(i,j); } } var time2:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { max = Math.max(i,j); } } var time3:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { m01 = Number(i<j); min = i* m01 + j*(1-m01); } } var time4:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { m01 = Number(i>j); max = i* m01 + j*(1-m01); } } var time5:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { min = (i-j) * Number(i<j) + j; } } var time6:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { max = (i-j) * Number(i>j) + j; } } var time7:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { min = (i<j) ? i : j; } } var time8:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { max = (i<j) ? j : i; } } var time9:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { __asm( GetLocal(i), GetLocal(j), Subtract, GetLocal(i), GetLocal(j), LessThan, ConvertDouble, Multiply, GetLocal(j), Add, SetLocal(min) ); } } var time10:uint = getTimer(); for(i= 0;i<REPS;i+=INC) { for(j= 0;j<REPS;j+=INC) { __asm( GetLocal(i), GetLocal(j), Subtract, GetLocal(j), GetLocal(i), LessThan, ConvertDouble, Multiply, GetLocal(j), Add, SetLocal(max) ) } } var time11:uint = getTimer(); startTest(); setTest( "Math.min:" , "float_min", time2-time1 ); setTest( "(i<j)?i:j" , "float_min", time8-time7 ); setTest( "min interp v1" , "float_min", time4-time3 ); setTest( "min interp v2" , "float_min", time6-time5 ); setTest( "min asm" , "float_min", time10-time9 ); setTest( "Math.max:" , "float_max", time3-time2 ); setTest( "(i<j)?j:i" , "float_max", time9-time8 ); setTest( "max interp v1" , "float_max", time5-time4 ); setTest( "max interp v2" , "float_max", time7-time6 ); setTest( "max asm" , "float_max", time11-time10 ); endTest(); }
What do you get ?
On my 2.3 GHz Intel Core i7, Flash Player 11.8, and Mac OS X 10.8 I’m getting horrible times with the
Math
class functions and radically better results with everything else, just like you. Also like you, I’m getting slightly worse results with “interp v2” than the other non-Math
versions. Everything else is about the same with some being faster in some runs of the code and slower in others. For my own usage, I’d probably go with what I think is the clearest and fastest non-Math
version, the simple ternary operator (wrapped in an[Inline]
function).