Last week’s article showed you a way to improve the performance of trig functions like `Math.sin` by almost 4x by using lookup tables. This week’s article will go even further and show you how to increase this speedup to over 10x!

The lookup table approach works by replacing a static function call like `Math.sin` with as little as a `Vector` index, an `int()` type conversion, and a multiply. The result was a whopping 4x improvement over `Math.sin` and its ilk. This was, of course, only true in the best case scenario where you had a number on [0,2π) and the performance advantages faded as you added uncertainties so that the number could be negative or greater than ± π.

Today’s article will discuss non-lookup table approaches offered by polygonal.de and yoambulante.com. In the polygonal.de case, there are two quality options—low and high—and no temporary variables are required. In contrast, the yoambulante.com way includes four quality options—low, acceptable, good, and excellent—and uses between 2 and 6 temporary variables, not to mention 2 to 6 constants.

So, let’s pit these various trig functions—`Math.sin`, `TrigLUT`, polygonal.de’s inline code, and yoambulante.com’s inline code—against each other and see who’s fastest. The polygonal.de code supported an arbitrary number of radians via conditionals, so I simply stripped out this support to generate the equivalent of `TrigLUT`‘s `valNormalized`, `valPositive`, and `valNormalizedPositive` functions. The yoambulante.com code worked on positive numbers as well as negative numbers, but lacked support for numbers greater than 2π, which I added in the `val` and `valPositive` case by using polygonal.de’s simple `if` statement. The result is this performance test:

```package
{
import flash.display.*;
import flash.utils.*;
import flash.text.*;

/**
*   Performance test app for faster ways of doing trig
*   @author Jackson Dunstan
*/
public class FastTrigTest extends Sprite
{
private var __logger:TextField = new TextField();
private function log(msg:*): void { __logger.appendText(msg + "\n"); }

public function FastTrigTest()
{
__logger.autoSize = TextFieldAutoSize.LEFT;

const DIGITS:uint = 2;
const STEP:Number = 0.05;
var beforeTime:int;
var afterTime:int;
var lut:TrigLUT = new TrigLUT(DIGITS, Math.sin);
var lutTable:Vector.<Number> = lut.table;
var lutPow:Number = lut.pow;
const REPS:int = 100000;
const NUM:int = int(Math.PI / STEP);
var theta:Number;
var temp:Number;
var i:int;
var j:int;
var result:Number;
const TWO_PI:Number = TrigLUT.TWO_PI;

//code by Alex Nino, yoambulante.com
//these are our factorial constant values
const f3:Number = (1 * 2 * 3);
const f5:Number = (f3 * 4 * 5);
const f7:Number = (f5 * 6 * 7);
const f9:Number = (f7 * 8 * 9);
const f11:Number = (f9 * 10 * 11);
const f13:Number = (f11 * 12 * 13);
//these are the variables where we store the powers,
//note they have to be defined each time we execute the function
var p3:Number;
var p5:Number;
var p7:Number;
var p9:Number;
var p11:Number;
var p13:Number;

/*
for (theta = -10; theta <= 10; ++theta)
{
log(theta + ": " + Math.sin(theta) + " ... " + yoambulanteExcellent(theta));
}
*/
log("Function,Time");

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = Math.sin(theta);
theta += STEP;
}
}
afterTime = getTimer();
log("Math.sin," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = lut.val(theta);
theta += STEP;
}
}
afterTime = getTimer();
log("TrigLUT.val," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = lut.valPositive(theta);
theta += STEP;
}
}
afterTime = getTimer();
log("TrigLUT.valPositive," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = lut.valNormalized(theta);
theta += STEP;
}
}
afterTime = getTimer();
log("TrigLUT.valNormalized," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = lut.valNormalizedPositive(theta);
theta += STEP;
}
}
afterTime = getTimer();
log("TrigLUT.valNormalizedPositive," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = theta >= 0
? lutTable[int((theta%TWO_PI)*lutPow)]
: lutTable[int((TWO_PI+theta%TWO_PI)*lutPow)];
theta += STEP;
}
}
afterTime = getTimer();
log("inline TrigLUT val," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = lutTable[int((theta%TWO_PI)*lutPow)];
theta += STEP;
}
}
afterTime = getTimer();
log("inline TrigLUT valPositive," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = theta >= 0
? lutTable[int(theta*lutPow)]
: lutTable[int((TWO_PI+theta)*lutPow)];
theta += STEP;
}
}
afterTime = getTimer();
log("inline TrigLUT valNormalized," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = lutTable[int(theta*lutPow)];
theta += STEP;
}
}
afterTime = getTimer();
log("inline TrigLUT valNormalizedPositive," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp < -3.14159265)
temp += 6.28318531;
else
if (temp >  3.14159265)
temp -= 6.28318531;

//compute sine
if (temp < 0)
result = 1.27323954 * temp + .405284735 * temp * temp;
else
result = 1.27323954 * temp - 0.405284735 * temp * temp;
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de val (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp >  3.14159265)
temp -= 6.28318531;

//compute sine
result = 1.27323954 * temp - 0.405284735 * temp * temp;
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de valPositive (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
//compute sine
if (theta < 0)
result = 1.27323954 * theta + .405284735 * theta * theta;
else
result = 1.27323954 * theta - 0.405284735 * theta * theta;
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de valNormalized (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
result = 1.27323954 * theta - 0.405284735 * theta * theta;
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de valNormalizedPositive (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp < -3.14159265)
temp += 6.28318531;
else
if (temp >  3.14159265)
temp -= 6.28318531;

//compute resulte
if (temp < 0)
{
result = 1.27323954 * temp + .405284735 * temp * temp;

if (result < 0)
result = .225 * (result *-result - result) + result;
else
result = .225 * (result * result - result) + result;
}
else
{
result = 1.27323954 * temp - 0.405284735 * temp * temp;

if (result < 0)
result = .225 * (result *-result - result) + result;
else
result = .225 * (result * result - result) + result;
}
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de val (high)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp >  3.14159265)
temp -= 6.28318531;

//compute resulte
result = 1.27323954 * temp - 0.405284735 * temp * temp;

if (result < 0)
result = .225 * (result *-result - result) + result;
else
result = .225 * (result * result - result) + result;
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de valPositive (high)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//compute resulte
if (temp < 0)
{
result = 1.27323954 * temp + .405284735 * temp * temp;

if (result < 0)
result = .225 * (result *-result - result) + result;
else
result = .225 * (result * result - result) + result;
}
else
{
result = 1.27323954 * temp - 0.405284735 * temp * temp;

if (result < 0)
result = .225 * (result *-result - result) + result;
else
result = .225 * (result * result - result) + result;
}
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de valNormalized (high)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
result = 1.27323954 * temp - 0.405284735 * temp * temp;

if (result < 0)
result = .225 * (result *-result - result) + result;
else
result = .225 * (result * result - result) + result;
theta += STEP;
}
}
afterTime = getTimer();
log("inline polygonal.de valNormalizedPositive (high)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp < -3.14159265)
temp += 6.28318531;
else
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com val (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalized (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valPositive (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalizedPositive (low)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp < -3.14159265)
temp += 6.28318531;
else
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
p7 = p5 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5) - (p7 / f7);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com val (acceptable)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
p7 = p5 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5) - (p7 / f7);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalized (acceptable)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
p7 = p5 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5) - (p7 / f7);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valPositive (acceptable)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
p7 = p5 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5) - (p7 / f7);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalizedPositive (acceptable)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp < -3.14159265)
temp += 6.28318531;
else
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
p7 = p5 * temp * temp;
p9 = p7 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com val (good)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
p7 = p5 * theta * theta;
p9 = p7 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalized (good)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
p7 = p5 * temp * temp;
p9 = p7 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valPositive (good)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
p7 = p5 * theta * theta;
p9 = p7 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalizedPositive (good)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp < -3.14159265)
temp += 6.28318531;
else
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
p7 = p5 * temp * temp;
p9 = p7 * temp * temp;
p11 = p9 * temp * temp;
p13 = p11 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9) - (p11 / f11) + (p13 / f13);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com val (excellent)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
p7 = p5 * theta * theta;
p9 = p7 * theta * theta;
p11 = p9 * theta * theta;
p13 = p11 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9) - (p11 / f11) + (p13 / f13);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalized (excellent)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
temp = theta;
//always wrap input angle to -PI..PI
if (temp >  3.14159265)
temp -= 6.28318531;
p3 = temp * temp * temp;
p5 = p3 * temp * temp;
p7 = p5 * temp * temp;
p9 = p7 * temp * temp;
p11 = p9 * temp * temp;
p13 = p11 * temp * temp;
result = temp - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9) - (p11 / f11) + (p13 / f13);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valPositive (excellent)," + (afterTime-beforeTime));

beforeTime = getTimer();
for (i = 0; i < REPS; ++i)
{
theta = 0;
for (j = 0; j < NUM; ++j)
{
p3 = theta * theta * theta;
p5 = p3 * theta * theta;
p7 = p5 * theta * theta;
p9 = p7 * theta * theta;
p11 = p9 * theta * theta;
p13 = p11 * theta * theta;
result = theta - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9) - (p11 / f11) + (p13 / f13);
theta += STEP;
}
}
afterTime = getTimer();
log("inline yoambulante.com valNormalizedPositive (excellent)," + (afterTime-beforeTime));
}
}
}```

Here is the test environment I ran the test on:

• Flex SDK (MXMLC) 4.1.0.16076, compiling in release mode (no debugging or verbose stack traces)
• Release version of Flash Player 10.3.181.14
• 2.4 Ghz Intel Core i5
• Mac OS X 10.6.7

And here are the results I got:

Function Time
Math.sin 238
TrigLUT.val 217
TrigLUT.valPositive 200
TrigLUT.valNormalized 121
TrigLUT.valNormalizedPositive 106
inline TrigLUT val 165
inline TrigLUT valPositive 163
inline TrigLUT valNormalized 73
inline TrigLUT valNormalizedPositive 68
inline polygonal.de val (low) 26
inline polygonal.de valPositive (low) 25
inline polygonal.de valNormalized (low) 24
inline polygonal.de valNormalizedPositive (low) 22
inline polygonal.de val (high) 32
inline polygonal.de valPositive (high) 28
inline polygonal.de valNormalized (high) 26
inline polygonal.de valNormalizedPositive (high) 24
inline yoambulante.com val (low) 27
inline yoambulante.com valNormalized (low) 22
inline yoambulante.com valPositive (low) 24
inline yoambulante.com valNormalizedPositive (low) 22
inline yoambulante.com val (acceptable) 26
inline yoambulante.com valNormalized (acceptable) 22
inline yoambulante.com valPositive (acceptable) 24
inline yoambulante.com valNormalizedPositive (acceptable) 22
inline yoambulante.com val (good) 27
inline yoambulante.com valNormalized (good) 22
inline yoambulante.com valPositive (good) 24
inline yoambulante.com valNormalizedPositive (good) 23
inline yoambulante.com val (excellent) 27
inline yoambulante.com valNormalized (excellent) 22
inline yoambulante.com valPositive (excellent) 25
inline yoambulante.com valNormalizedPositive (excellent) 22

The differences between these various approaches are much more apparent as a graph: We can easily see that the inline versions are far faster than the lookup table version, even in its inline form, due to their lack of a `Vector` access. They each benefit, albeit much more slightly, from the limited-scope versions such as `valPositive` and `valNormalized`. Between th two inline approaches, the polygonal.de one seems to be superior for these reasons:

1. On average, it is 8% faster.
2. It is more accurate, especially around ± 2π
3. It requires no temporary variables, which is a big advantage when not computing tons of trig values in one huge run
4. Its quality is quite good, even in the “low” version

For more on the above quality claims, let’s have a look at a small demo app. I’ve ported over the `TrigLUT` demo to support all of the approaches from the performance test. To demonstrate the accuracy downsides of the yoamulante.com version, I’ve adjusted the graphed range from [0,2π) to (-π,&pi). Here’s the demo’s source code followed by a live SWF version:

```package
{
import flash.display.*;
import flash.filters.*;
import flash.events.*;
import flash.text.*;
import flash.geom.*;

/**
*   Demonstration app for faster ways of doing trig
*   @author Jackson Dunstan
*/
public class FastTrigDemo extends Sprite
{
private static const TEXT_FORMAT:TextFormat = new TextFormat("_sans", 11);

private static const SINE_LUT_LOW:TrigLUT = new TrigLUT(2, Math.sin);
private static const SINE_LUT_HIGH:TrigLUT = new TrigLUT(3, Math.sin);

private static const APPROX_METHOD_CATEGORIES:Array = [
[
["TrigLUT"],
["low","lutLow"],
["high","lutHigh"]
],
[
["polygonal.de"],
["low","polygonalDeLow"],
["high","polygonalDeHigh"]
],
[
["yoambulante.com"],
["low","yoambulanteLow"],
["acceptable","yoambulanteAcceptable"],
["good","yoambulanteGood"],
["excellent","yoambulanteExcellent"]
]
]

private var bmd:BitmapData;
private var bmdRect:Rectangle;
private var approxFunc:Function;

public function FastTrigDemo()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;

var x:Number;
var y:Number = 0;

var prompt:TextField = new TextField();
prompt.y = y;
prompt.defaultTextFormat = TEXT_FORMAT;
prompt.htmlText = "<b>Approximation Method:</b>";
prompt.autoSize = TextFieldAutoSize.LEFT;
prompt.selectable = false;

var buttons:Array = [];
for each (var category:Array in APPROX_METHOD_CATEGORIES)
{
var categoryName:String = category.shift();
var categoryNameTF:TextField = new TextField();
categoryNameTF.mouseEnabled = false;
categoryNameTF.defaultTextFormat = TEXT_FORMAT;
categoryNameTF.htmlText = "<b>" + categoryName + "</b>";
categoryNameTF.autoSize = TextFieldAutoSize.LEFT;
categoryNameTF.selectable = false;

for each (var choice:Array in category)
{
var choiceName:String = choice;
var choiceFuncName:String = choice;

var tf:TextField = new TextField();
tf.mouseEnabled = false;
tf.defaultTextFormat = TEXT_FORMAT;
tf.text = choiceName;
tf.autoSize = TextFieldAutoSize.LEFT;
tf.selectable = false;

var button:Sprite = new Sprite();
button.buttonMode = true;
button.name = choiceFuncName;
button.graphics.beginFill(0xE6E2D1);
button.graphics.endFill();
MouseEvent.CLICK,
function(ev:MouseEvent): void
{
var clickButton:Sprite = ev.currentTarget as Sprite;
for each (var otherButton:Sprite in buttons)
{
otherButton.filters = [new BevelFilter()];
otherButton.buttonMode = true;
otherButton.mouseEnabled = true;
}
clickButton.filters = [];
clickButton.buttonMode = false;
clickButton.mouseEnabled = false;

approxFunc = FastTrigDemo[clickButton.name];
redraw();
}
);
button.x = x;
button.y = y;

buttons.push(button);
}

categoryNameTF.y = y + (button.height-categoryNameTF.height) / 2;
}

+ "Red line: approximation\n"
+ "Demo by <font color=\"#0071BB\"><a href=\""
+ "http://jacksondunstan.com/articles/1213"
+ "\">JacksonDunstan.com</a></font>";

var offset:int = stage.stageHeight - height;
for (var i:int; i < numChildren; ++i)
{
getChildAt(i).y += offset;
}

this.bmd = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0xffffffff);
this.bmdRect = new Rectangle(0, 0, this.bmd.width, this.bmd.height);
buttons.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}

private function redraw(): void
{
var bmd:BitmapData = this.bmd;
bmd.lock();
var w:int = bmd.width;
var h:int = bmd.height;
var halfH:int = h / 2;
var stepTheta:Number = TrigLUT.TWO_PI / w;
var theta:Number = -Math.PI;
for (var x:int = 0; x < w; ++x)
{
for (var y:int = 0; y < h; ++y)
{
bmd.setPixel(
x,
halfH + Math.sin(theta)*halfH,
0xff000000
);
bmd.setPixel(
x,
halfH + this.approxFunc(theta)*halfH,
0xffff0000
);
}
theta += stepTheta;
}
bmd.unlock();
}

private static function lutLow(x:Number): Number
{
return SINE_LUT_LOW.valNormalized(x);
}

private static function lutHigh(x:Number): Number
{
return SINE_LUT_HIGH.valNormalized(x);
}

private static function polygonalDeLow(x:Number): Number
{
//always wrap input angle to -PI..PI
if (x < -3.14159265)
x += 6.28318531;
else
if (x >  3.14159265)
x -= 6.28318531;

//compute sine
if (x < 0)
return 1.27323954 * x + .405284735 * x * x;
else
return 1.27323954 * x - 0.405284735 * x * x;
}

private static function polygonalDeHigh(x:Number): Number
{
//always wrap input angle to -PI..PI
if (x < -3.14159265)
x += 6.28318531;
else
if (x >  3.14159265)
x -= 6.28318531;

//compute sine
if (x < 0)
{
var sin:Number = 1.27323954 * x + .405284735 * x * x;

if (sin < 0)
return .225 * (sin *-sin - sin) + sin;
else
return .225 * (sin * sin - sin) + sin;
}
else
{
sin = 1.27323954 * x - 0.405284735 * x * x;

if (sin < 0)
return .225 * (sin *-sin - sin) + sin;
else
return .225 * (sin * sin - sin) + sin;
}
}

{
//code by Alex Nino, yoambulante.com
//these are our factorial constant values
const f3:Number = (1 * 2 * 3);
const f5:Number = (f3 * 4 * 5);
//these are the constiables where we store the powers,
//note they have to be defined each time we execute the function
return rad - (p3 / f3) + (p5 / f5);
}

{
//code by Alex Nino, yoambulante.com
//these are our factorial constant values
const f3:Number = (1 * 2 * 3);
const f5:Number = (f3 * 4 * 5);
const f7:Number = (f5 * 6 * 7);
//these are the constiables where we store the powers,
//note they have to be defined each time we execute the function
return rad - (p3 / f3) + (p5 / f5) - (p7 / f7);
}

{
//code by Alex Nino, yoambulante.com
//these are our factorial constant values
const f3:Number = (1 * 2 * 3);
const f5:Number = (f3 * 4 * 5);
const f7:Number = (f5 * 6 * 7);
const f9:Number = (f7 * 8 * 9);
//these are the constiables where we store the powers,
//note they have to be defined each time we execute the function
return rad - (p3 / f3) + (p5 / f5) - (p7 / f7) + (p9 / f9);
}

{
//code by Alex Nino, yoambulante.com
//these are our factorial constant values
const f3:Number = (1 * 2 * 3);
const f5:Number = (f3 * 4 * 5);
const f7:Number = (f5 * 6 * 7);
const f9:Number = (f7 * 8 * 9);
const f11:Number = (f9 * 10 * 11);
const f13:Number = (f11 * 12 * 13);
//these are the constiables where we store the powers,
//note they have to be defined each time we execute the function
In short, the polygonal.de source code is simple, accurate, and extremely fast. I recommend using it either manually (copy/paste as I have) or via the `FastMath` class in Apparat. By doing so, you’ll speed up your trig code by over 10x! In the case that you need even greater precision, there is always `TrigLUT` if you can live with the 3x slowdown.