If you find out any problems, or want to give any ideas into this, don't hesitate to contact me! :)
Written by Auti Celeste <me@auti.one> • Sources: Wikipedia, Wolfram, Khan Academy, Mathematics StackExchange
Sine function sin(x)
The sine function indicates the distance from the "back side" of a unit circle to the center y level of the circle.
Since supposedly circles have infinite sides, all trigonometric functions are infinitely iterable, and therefore with most results not being 100% exactly known, similar to the digits of pi, which are also infinite, hence it's also an irrational number, since it's got infinite digits/decimal bits and cannot be represented by a fraction.
As it's unpractical to perform infinite iterations for the calculations necessary for determine the value of a trigonometric function, we can only have an approximation, which is determined by the "acc" (accuracy) arguments you will see on the following implementations I've written.
From some quick and simple research, I've found out that you can perform a sine function calculation like this:
By then, I didn't really understand why our exponent begins at 3 and goes on adding by 2 every term, but I think I got the gist of it and saw it's a series with a quite obvious pattern.
While researching, I've found out about a thing called Taylor series/expansion, of which from what I understood, is a reasonable way to represent these infinite series, and also how some computers and other electronic devices actually calculate the trigonometric functions, and which lead me to this formula:
That did give me some better enlightening, when I actually put everything together and found out what it meant. From what I figured:
- represents the sum series, with being the infinite sum series;
- is the iteration count/index, which I usually define as
iinfor-loops in my code;
And I'm not too sure about this one, but I tried to make sense of it: The initial exponent shown in the first formula being has some correlation with the part, where , but I still need some clearance in that.
After I figured out enough of what was going on, I began my attempts on writing an implementation for the sine function. I wrote them all in TypeScript, running it on Deno, and also comparing it to the natively built-in Math.sin method, and rewriting them a few times every successful run.
I tried to calculate the sine of , having the LIB field being the included Math.sin function and the LOC field being my local implementation. Below are my attempts that didn't go wrong:
#1
const sin = (x: number): number => {
let fac = 0; // operation factor
let res = 0;
const acc = 8; // accuracy -- ^2 best prec. (?)
for (let i = 0; i < acc; i += 1) {
fac = Math.pow(-1, i);
const num = 2 * i + 1;
const den = fact(num);
res = res + (fac * Math.pow(x, num)) / den;
}
return res;
};LIB: 1, LOC: 1#2
const sin = (x: number, it = 16) => {
let fac = 0; // addition factor
let res = 0;
for (let i = 0; i < it; i++) {
fac = Math.pow(-1, i);
const num = 2 * i + 1;
const den = fact(num);
res = res + (fac * Math.pow(x, num)) / den;
}
return res;
};LIB: 1, LOC: 1.0000000000000002#3
const sin = (x: number, acc = 10): number => {
let fac = 0;
let res = 0;
for (let i = 0; i < acc; i += 1) {
fac = Math.pow(-1, i); // make it alternate the operation factor (b. -/+)
const pow = 2 * i + 1; // make +2 addition to the expoent
const num = Math.pow(x, pow); // fraction numerator
const den = fact(pow); // fraction denominator
res = res + (fac * num) / den;
}
return res;
};LIB: 1, LOC: 1#4
const sin = (x: number, acc = 10): number => {
let fac = 0;
let res = 0;
for (let i = 0; i < acc; i += 1) {
fac = Math.pow(-1, i);
const exp = 2 * i + 1; // x's expoent
const num = Math.pow(x, exp);
const den = fact(exp);
res = res + fac * (num / den);
}
return res;
};LIB: 1, LOC: 1This was quite fun for me and I did all of these rewrites, so I could both fixate and remember it, as well as try to figure out potential improvements, such as from my initial (and unsuccessful) attempts that used an alternating boolean and a direct power factor for switching the operation sign and generating the numerator to be used in the formula.
You can see I tried to make the formula declaration very verbose/explicit by naming the variables conventionally, and play around with the iteration counts. Turns out, it seems to be less fluctuant if you only run it for about 10 times, because as you could see on run #2, my program returned 1.0000000000000002 instead of 1, which indicates something is making it get a bit lost on the floating point number calculations.
A fun fact is that you can get a number with a similar digit count by doing 0.1 + 0.2 in some programming languages, which might return you something like 0.30000000000000004, which is due to how computers do floating point math, being able to only store integers natively and whatnot. This is a bit more in-depth computer science content, of which I'm not covering in this article, but you can check out more on computers' floating point math here.
Cosine function cos(x)
After I found myself being happy enough with the results from my attempts on the sine function, I tried out cosine this time, even though the challenge proposed to me only needs you to make an implementation of a single trigonometric function.
I don't remember where, but this seemed to be true after I looked it up on some math-related communities and websites, that sine and cosine have a similar relation as they both relate to that "back side of a unit circle" thing I said at the beginning of this article. Apparently, their relation can be expressed with this formula:
So, seemingly, you have the same (or a very similar) series pattern as you do with the sine function, as you could also figure out by the functions' names, where is the distance to the "back side" of the unit circle, and is the distance to that said "back side", from what I understood. You can even make out coordinates of a "meeting point" using both results!
With additional research, I found out that due to the expression, it turns out that the cosine function actually uses even numbers in the series' exponents, instead of odd numbers in sine, like this:
Then for the exponent formula, we can take out the part and make it become only . Eventually, I'd just copy and adapt the formula and come out with this:
To confirm my hypotheses on the relation of and being , I made a really lazy and sloppy implementation of a cos function, which just called sin with , but this time, my input value was instead of , because apparently that's and my attempts on doing the relation trickery for cos didn't go out well with that as an input, having results like these:
LIB D: 6.123233995736766e-17
LIB I: 1.2246467991473532e-16
LOC : 3.3280567300670973e-16With the LIB fields using the built-in Math library, D calculating directly with Math.cos and I calculating indirectly with , and the LOC field being my local implementation (my previous sin function with as input too). Note that in this batch of tests.
So, testing it with as value, we get more consistent results:
LIB D: -0.4161468365471424
LIB I: -0.41614683654714235
LOC : -0.4161468442894976My implementation starts diverging from the others after about the 8th decimal point, whereas the native library's direct and indirect results start diverging after the 15th decimal point, but I guess it's precise enough? Might be, as my implementation's results seemed to get closer to the library's when I increased the loop count, though with not much difference after the 12th to 15th iteration:
[15×]
LIB D: -0.4161468365471424
LIB I: -0.41614683654714235
LOC : -0.4161468365471429[20×]
LIB D: -0.4161468365471424
LIB I: -0.41614683654714235
LOC : -0.4161468365471429 At least that assures me of the relation between and , and finally leads me to make an actual direct implementation, without reutilizing sin and based off the Taylor expansion formula given for :
The process turned out to be very quick, as I got the hang of it while I was still doing my implementations on sin, and the formula is extremely similar. In fact, it's pretty much the same, except you don't add anything to the multiplied exponent.
#1
const cos = (x: number, acc = 15) => {
let fac = 0;
let res = 0;
for (let i = 0; i < acc; i += 1) {
fac = Math.pow(-1, i);
const exp = 2 * i;
const num = Math.pow(x, exp);
const den = fact(exp);
res = res + fac * (num / den);
}
return res;
};LIB D: -0.4161468365471424, LIB I: -0.41614683654714235, LOC: -0.41614683654714246I don't have much to say about this one, as it's almost the same formula. I got used to my structuring convention and just reapplied it on this program. One thing I have to note is that you might stumble upon problems depending on how you're factorializing the exponent. My initial factorial function was written like this:
const fact = (x: number): number => {
if (x == 1 || x == 0) return 1;
return fact(x - 1) * x;
}Which works out pretty well and gives you the expected result, most times. As I didn't have any problems with that version while testing out with sin, I didn't notice a very important fact that you can't have the factorial of any number below zero, which was not predicted in this implementation, so I'd only perceive this flaw after my tests with cos. That doesn't mean you get a negative exponent on my cos function, but my implementation does get as an exponent, which makes it re-run itself with , then since it can't calculate the factorial of a negative number, it re-runs itself with , and so on, which makes it error with a call stack overflow, that happens when you recursively call a function too much that it stops to prevent an infinite loop and memory stacking.
Eventually, I'd switch to an implementation that used for-loops instead of function recursiveness, like this:
const fact = (x: number): number => {
if (x === 0 || x === 1) return 1;
for (let i = x - 1; i >= 1; i -= 1) x *= i;
return x;
};This function doesn't enter an infinite loop (at least, from what I've tested) and this one actually has a check for negative numbers, so they don't get impossibly factorialized.
Final Notes
My teacher only requested a calculator with a single trigonometric function, but I came to make both and because of their relation and also because I have a little bit more experience with these than with the others trigonometric functions, such as or whatnot.
Writing this document and the code implementations were really fun for me and got me really immersed into this, taking from me a few hours in programming and researching, and for the first time in a while, I've actually learnt something new and a bit more advanced in mathematics, which is really cool for me! You can also check out a practical usage of this on a simple (and probably broken) sine & cosine calculator here.
I also thank you for reading. This took me some time to look up all the info and put together everything I figured out from it, so it's been quite an effort. You reading this to the end means a lot, as it's the kind of subject not many people are genuinely interested in knowing about or paying attention to.