Introduction
Have you ever wondered how your computer calculates certain mathematical functions, like division, or trigonometric functions like sine or cosine? Well, for some of these mathematical functions, there exist useful formulas to calculate very accurate results pretty easily. For sine and cosine, one commonly used formula looks like this:
And for cosine:
Note that the input of each function is in radians, not degrees.
The series used in both formulas is called a Maclaurin series (a type of Taylor series), and can be derived from the sine and cosine functions with a series expansion.
How the Programs Work
I've written programs to implement these two computations in three major scripting languages: Python, JavaScript, and Perl. These programs do not include any built-in trig functions or other utilities except the use of the in-built π constant in some cases. All code is CC0 licensed.
The approach I used creates a generalized function called computeSeries
which takes in x as the number to calculate the sine or cosine of, a starting number in the series (x for sine and 1 for cosine), and the exponent and factorial base in the first term of the series (3 for sine and 2 for cosine).
In calculating each series, I found that only about 10 terms in the series were needed to get a decently accurate result.
The programs additionally include utility functions for sine and cosine functions in degrees. The end of each program also includes a few tests of each function, which work as expected.
In Python
Feel free to view the below code as a GitHub Gist.
from math import pi
# round a number (x) to nearest 10 digits
def rounded(x):
return round(x, 10)
# get the factorial of a number (x)
# factorial(x) is the product of every number from 1 to N inclusive
def factorial(x):
n = 1; # n is the result
# multiply n by every number from 1 to x inclusive
for i in range(2, x + 1):
n *= i
return n
""" get the result of the cos and sin formulas
where the functions are sin(x radians) or cos(x radians),
n is the start value (n = x for sin, n = 1 for cos), and
i_start is the exponent and factorial base in the first term """
def computeSeries(x, n, i_start):
iterations = 20 # iterations is twice the amount of terms to use
multiplier = 1
for i in range(i_start, i_start + iterations, 2): # i increases by 2 each term
multiplier *= -1 # alternates between addition and subtraction each term
next_term = (x**i) / factorial(i) # each term is (x^i) / i!
n += multiplier * next_term # add or subtract from final result
return n
# get sin of x radians
def sin(x):
return rounded(computeSeries(x, x, 3))
# get cos of x radians
def cos(x):
return rounded(computeSeries(x, 1, 2))
# get sin of x degrees
def sinDeg(x):
return sin(x * pi / 180)
# get cos of x degrees
def cosDeg(x):
return cos(x * pi / 180)
# test the functions
print(sin(pi / 6)); # 0.5
print(sinDeg(45)); # 0.7071
print(sinDeg(52)); # 0.78801
print(cos(pi / 3)); # 0.5
print(cosDeg(45)); # 0.7071
print(cosDeg(52)); # 0.615661
In JavaScript
Feel free to view the below code as a GitHub Gist.
// round a number (x) to nearest 10 digits
const rounded = (x) => {
return parseFloat(x.toFixed(10));
}
// get the factorial of a number (x)
// factorial(x) is the product of every number from 1 to x inclusive
const factorial = (x) => {
let n = 1; // n is the result
// multiply n by every number from 1 to x inclusive
for(let i = 2; i <= x; i++) {
n *= i;
}
return n;
}
/* get the result of the cos and sin formulas
where the functions are sin(x radians) or cos(x radians),
n is the start value (x for sin, 1 for cos), and i_start
is the exponent and factorial base in the first term */
const computeSeries = (x, n, i_start) => {
const iterations = 20; // iterations is twice the amount of terms to use
let multiplier = 1;
let i = i_start;
while(i < i_start + iterations) {
multiplier *= -1; // alternates between addition and subtraction each iteration
const next_term = (x**i) / factorial(i); // each term is (x^i) / i!
n += multiplier * next_term // add or subtract from final result
i += 2 // i increases by 2 each term
}
return n
}
// get sin of x radians
const sin = (x) => {
return rounded(computeSeries(x, x, 3));
}
// get cos of x radians
const cos = (x) => {
return rounded(computeSeries(x, 1, 2));
}
// get sin of x degrees
const sinDeg = (x) => {
return sin(x * Math.PI / 180);
}
// get cos of x degrees
const cosDeg = (x) => {
return cos(x * Math.PI / 180);
}
// test the functions
console.log(sin(Math.PI / 6)); // 0.5
console.log(sinDeg(45)); // 0.7071
console.log(sinDeg(52)); // 0.78801
console.log(cos(Math.PI / 3)); // 0.5
console.log(cosDeg(45)); // 0.7071
console.log(cosDeg(52)); // 0.615661
In Perl
Feel free to view the below code as a GitHub Gist.
#!/usr/bin/perl
use warnings;
$pi = 3.14159265358979323;
# get the factorial of a number (x)
# factorial(x) is the product of every number from 1 to N inclusive
sub factorial {
my ($x) = @_;
my $n = 1; # n is the result
# multiply n by every number from 1 to x inclusive
my @nums_to_multiply = (1..$x);
for(@nums_to_multiply){
$n *= $_;
}
return $n;
}
=begin
get the result of the cos and sin formulas
where the functions are sin(x radians) or cos(x radians),
n is the start value (n = x for sin, n = 1 for cos), and
i_start is the exponent and factorial base in the first term
=cut
sub computeSeries {
$ITERATIONS = 20; # iterations is twice the amount of terms to use
my ($x, $n, $i_start) = @_;
my $multiplier = 1;
$i = $i_start;
while($i < $i_start + $ITERATIONS) {
$multiplier *= -1; # alternates between addition and subtraction each term
$n += $multiplier * (($x**$i) / factorial($i)); # add or subtract ((x^i) / i!) from final result
$i += 2; # i increases by 2 each term
}
return $n;
}
# get sin of x radians
sub mySin {
my ($x) = @_;
return computeSeries($x, $x, 3);
}
# get cos of x radians
sub myCos {
my ($x) = @_;
return computeSeries($x, 1, 2);
}
# get sin of x degrees
sub sinDeg {
my ($x) = @_;
return mySin($x * $pi / 180);
}
# get cos of x degrees
sub cosDeg {
my ($x) = @_;
return myCos($x * $pi / 180);
}
# test the functions
print(sin($pi / 6) . "\n"); # 0.5
print(sinDeg(45) . "\n"); # 0.7071
print(sinDeg(52) . "\n"); # 0.78801
print(cos($pi / 3) . "\n"); # 0.5
print(cosDeg(45) . "\n"); # 0.7071
print(cosDeg(52) . "\n"); # 0.615661
Conclusion
I hope this helps in understanding how computers and languages would go about calculating trigonometric functions like sine and cosine. If you'd like to read more about how exactly mathematical formulas used to calculate the trig functions are derived, I would recommend taking a look at the videos on Taylor and Maclaurin series by Khan Academy.
These programs are all licensed under the CC0 license, so feel free to use any of the code as you wish, without attribution.
Thanks for scrolling.
— Gabriel Romualdo, December 31, 2020
Top comments (5)
One thing you should play with is 'memoization' and recursion. For those not familiar with the term, it is the caching of function results using the arguments as a key. In Perl, it's as simple as using the Memoize module. Not sure about the other languages' implementations.
Now, when calculating the factorial in a loop, memoization may not save you much. But in a recursive implementation, memoization is a tremendous win.
Something for you to have in your back pocket.
As for your Perl, it is very good - clear and very concise. One improvement for space and copy time considerations:
can be replaced with
That will save you the memory and the time to copy that series into the array.
A second suggestion:
can be replaced with
No need in recalculating the same number over and over in the loop.
Excellent article.
Also, by adding
use strict;
at the top of your Perl code, you will get some errors about undefined vars. Just addmy
s in the appropriate places and you'll be fine. Always includeuse strict;
in addition touse warnings;
. It will catch unintended overlapping global usage of variables, which is a bear to debug, especially as the code gets longer. Again, excellent article.You could do better by using the
reduce
functions available in each language: Python, JavaScript, PerlAwesome post! I think as developers we can often forget the fundamentals and use already existing libraries, which can lead to dependency hell like in Nose.js.
Articles like these are a great way to help us better understand these basic functions. Thanks for sharing!
Thanks! I'm glad you enjoyed the article, and happy new year!
— Gabriel