Readers, I screwed up royally… I had published this post yesterday, only to accidentally overwrite it this morning. Add to that not having backed it up makes me a silly little programmer/blogger… So I’m going to write this out again, but this time, the way it should have been done, along with backing it up just in case I stupidly do this again.

So, last week we touched upon the derivation of a square root function that relied on nothing but fat arrow functions and standard operators – no reserved words, no native methods, no JavaScript libraries, nothing fancy whatsoever. We used Newton’s method of approximation to get closer and closer to the value, and we succeeded in doing so. We even streamlined it so that it looked pretty bad-ass…

Today I want to touch upon applying a similar technique to replicating the three basic trigonometric functions found in JavaScript’s Math library, but rendering them (as in our previous post) such that they are also void of any reserved words, native methods, or JavaScript libraries – just straight off fat arrow functions and standard operators. The three I want to focus on are:

- The sine function, Math.sin();
- The cosine function, Math.cos(); and
- The tangent function, Math.tan()

We’ll tackle them in this order as there is a natural progression from the first to the last function. So let’s start off with the sine function.

## Sine Function

The sine function has been determined to be a sum of terms, as discovered by the Taylor series. The Taylor series for the sine function is as follows:

As you can see there’s two particular mathematical functions at play with the derived sum:

- The factorial, indicated by the
Component; and**(2n+1)!** - The exponentiation or power, indicated by both
and**(-1)**^{n}.**x**^{2n+1}

We can replicate both functions as recursive JavaScript functions that we can call upon later:

_fac = x => x < 2 ? 1 : x * _fac(x - 1);

_pow = (x, y) => y-- ? x * _pow(x, y) : 1;

Note that both of these functions are based solely on integer calculations, so no 3.4! or 2.5^{3.1} happening here – there’s no need for this with what we’re trying to do today.

From these two functions and the value of a single term of the sine Taylor series, you can derive the following:

S = (x,n) => _pow(-1, n) * _pow(x, 2 * n + 1) / _fac(2 * n + 1)

Now, we need to just add the first *n* terms, wait a second, we’re talking an infinite number of terms! Let’s approximate that. I’ve performed enough testing to verify that the first 100 terms should be enough. Technically, it’s actually overkill after the first 14 or so terms, but depending on the system calculating the several decimal places, after 100 terms, you’re dealing with zero underflow because the value of the later ** n** terms converge so close to zero that the system treats these values as zero because it can’t think that small. Anyhow, here’s the function, implementing the code for the single terms of the sine Taylor series, all added up:

_sin = function(x) { for(n = 100, z = 0; ~(--n); z += _pow(-1, n) / _fac(2 * n + 1) * _pow(x, 2 * n + 1)); return z; }

Two things to note in this function:

- The
component. This is a creative way to use a loop from**~(–n)***n*to 0, and also decrement at the same time after each execution prior to the condition test. The tilde is a bitwise NOT operator (BNO) which applies the following algorithm:- Take N, and add 1;
- Negate the result
- Return the result
- Put simply it applies –(N+1) to the value provided, so ~6 becomes -7, for example. Towards the end of the loop, counting down from 100 to 0, the application of the BNO on 3, 2, 1, 0 yields -4, -3, -2, -1, which are all truthy values, so the loop continues. It is only after then when
*n*reaches ‑1, ~(-1) becomes –(-1+1) = 0, a falsy value, and so the loop stops.

- We’ve moved the actual actions of the for loop into the last parameter. This works well as it allows us to chain a lot of stuff up within the for loop. I’ll touch on this in a later post.

This is great, but that for loop is a little nasty… How about we make this a recursive function and hoist n and z up into the parameter section?

_sin = (x, n = 100, z = 0) => ~--n ? _sin(x, n, z +_pow(-1, n) / _fac(2 * n + 1) * _pow(x, 2 * n + 1)) : z;

What have we done exactly? We’ve hoisted up the ** n** and

**values, and set their defaults, seeing the first iteration of the function only calls on**

*z***. We’ve also ensured that in later iterations that**

*x***and**

*n***are called. We’ve removed the parentheses from the ternary function’s condition as it’s not necessary given the nature of the condition. Also the value of**

*z***being passed is already defined as**

*z***plus the next Taylor series term, so we don’t need to re-assign it back to**

*z***as it’s already done when we recurse. Finally, if the condition becomes falsy, the value of**

*z***passed into the function is the value returned. By that stage we should have converged on the value of**

*z**sin(x)*to the best of the compiler’s ability.

We can go further with this… Remember what I said about the BNO?

~n = -(n+1)

So if we were to negate both sides, we’d get:

-~n = n+1, which makes sense as -~6 would make 7. This means to increase a number by one, we can stick **-~** in front of it…

We technically can then add **n** to both sides and get

n-~n = 2n+1… Look familiar? We have it in twice in our code, as **2 * n + 1**. So let’s substitute and re-write the code:

_sin = (x, n = 100, z = 0) => ~--n ? _sin(x, n, z +_pow(-1, n) / _fac(n-~n) * _pow(x, n-~n)) : z;

That looks gorgeous… But how does that rate against the real deal, i.e. Math.sin(x)?

Let’s test this against four values of x:

- π radians (180 degrees), sin π = 0
- π/2 radians (90 degrees), sin π/2 = 1
- π/3 radians (60 degrees), sin π/3 = √(3)/2 = 0.866025403…
- π/4 radians (45 degrees), sin π/4 = √(2)/2 = 0.707106781…

Let’s get testing:

### Test 1

_sin(π) = 0 Math.sin(π) = 1.2246467991473532e-16

Note that JavaScript’s native library can’t hack it here… It’s hit 0.00000000000000012246467991473532 but gives up, as though it hasn’t gone far enough. If it was indeed using a similar approach to what we’ve used, it doesn’t appear to have done enough iterations. I’ve done a little tinkering with the default value of *n* in our _sin() function, and determined that once we hit 14 iterations, _sin(π) hits zero underflow and coerces to zero. Anything fewer than that, and you get something similar to JavaScript’s attempt.

Winner: _sin()

### Test 2

_sin(π/2) = 1 Math.sin(π/2) = 1

Both seem to have handled it, so let’s call this a tie.

Winner: TIE

### Test 3

_sin(π/3) = 0.8660254037844386 Math.sin(π/3) = 0.8660254037844386

Again, a tie… both are returning the expected value.

Winner: TIE

### Test 4

_sin(π/4) = 0.7071067811865475 Math.sin(π/4) = 0.7071067811865475

Yet again, another tie… both are returning the expected value.

Winner: TIE

Overall winner: _sin()

This is great! Could we apply a similar result with the cosine function equivalent? Let’s take a look…

## Cosine Function

The cosine function has a similar formula, based off the Taylor series. This is what the Taylor series says about the cosine function:

Almost identical, save that any instance of *2n+1* is replaced with just *2n*. if that’s the case, we can derive the _cos() function quite readily:

_cos = (x, n = 100, z = 0) => ~--n ? _cos(x, n, z +_pow(-1, n) / _fac(2*n) * _pow(x, 2*n)) : z;

Let’s test this out with the same tests as before, remembering we’ll get different results with cosines:

- π radians (180 degrees), sin π = -1
- π/2 radians (90 degrees), sin π/2 = 0
- π/3 radians (60 degrees), sin π/3 = 0.5
- π/4 radians (45 degrees), sin π/4 = √(2)/2 = 0.707106781…

### Test 1

_cos(π) = -1.0000000000000009 Math.cos(π) = -1

Yuck… our function loses out here. Even if we stretch the number of iterations to twice what we have defined here, zero underflow does not kick in… JavaScript’s native library wins out this time.

Winner: Math.cos()

### Test 2

_cos(π/2) = 0 Math.cos(π/2) = 6.123233995736766e-17

Native library has lost out this time, returning 0.00000000000000006123233995736766 and not hitting zero underflow, like how our derived function has.

Winner: _cos()

### Test 3

_cos(π/3) = 0.5000000000000001 Math.cos(π/3) = 0.5000000000000001

A tie… Even though the result returned by both functions has an error of 5 × 10^{-15 }%. Not ideal, but “acceptable”… kinda… Zero underflow has not been kind to either of these…

Winner: TIE, with caveats.

### Test 4

_cos(π/4) = 0.7071067811865475 Math.cos(π/4) = 0.7071067811865476

Practically another tie… both are returning the expected value, well one of them is closer to the other… by about 1.4 × 10^{-14 }%… WolframAlpha states that √(2)/2 = 0.7071067811865475244… And thus the derived function wins out.

Winner: _cos()

Overall winner: _cos()

So aside from the slight deviations by 14 to 15 orders of magnitude, _cos() has a strong case… Let’s take a look at the tangent function…

## Tangent Function

This is even easier, because the tangent of an angle is equal to the sine of the angle divided by the cosine of the angle, we can define the function like this:

_tan = x => _sin(x) / _cos(x);

Let’s see how it stacks up with our tests…

- π radians (180 degrees), tan π = 0
- π/2 radians (90 degrees), tan π/2 = ∞ as sin(π/2) = 1, cos(π/2) = 0
- π/3 radians (60 degrees), tan π/3 = √(3)
- π/4 radians (45 degrees), tan π/4 = 1

### Test 1

_tan(π) = 0 Math.tan(π) = -1.2246467991473532e-16

JavaScript Math library, go home, you’re drunk… Once again, the native method fails, returning ‑0.00000000000000012246467991473532 and can’t even hit zero underflow. Meanwhile our derived function has it all worked out…

Winner: _tan()

### Test 2

_tan(π/2) = Infinity Math.tan(π/2) = 16331239353195370

The native method has lost big time, returning a ridiculously high number, which is basically the reciprocal of Math.cos(π/2), seeing the Math.sin(π/2) returned 1 correctly. The returned value of 1/0.00000000000000006123233995736766 has made me question the validity of the JavaScript Math library. Meantime, our derived function hits the mark perfectly seeing it correctly gave us the correct values for _sin(π/2) and _cos(π/2).

Winner: _tan()

### Test 3

_cos(π/3) = 1.7320508075688767 Math.cos(π/3) = 1.7320508075688767

A tie… Even though the result returned by both functions is off from the square root of 3 by has an error of about 3.4 × 10^{-14 }%. Not ideal, but also “acceptable”…

Winner: TIE, with caveats.

### Test 4

_cos(π/4) = 1 Math.cos(π/4) = 0.9999999999999999

Practically another tie… But the derived function wins as the native method keels over as it can’t tick over to the 1 mark… The JavaScript library is really letting me down here… Must have been that slight deviation from Math.cos() that put it out of whack…

Winner: _tan()

Overall winner: _tan()

So, what have we learned? Our derived functions seem to be more reliable than the existing JavaScript Math library… Could it be because we’ve established an algorithm that’s correctly executed ad absurdum? I’m honestly not sure what the answer is, but as far as we can tell, it seems more likely this is more accurate, by far.

My next post will be focusing on the derivation of two well-known irrational numbers, π and *e* using the same methodology. Until then, please stay quirky!