DEV Community

loading...
Cover image for Programs to Compute Trig Functions in Python, JavaScript, & Perl (with Maclaurin Series)

Programs to Compute Trig Functions in Python, JavaScript, & Perl (with Maclaurin Series)

xtrp profile image Gabriel Romualdo Originally published at xtrp.io ・5 min read

Originally published here at xtrp.io, my blog about computer science and just about anything programming.

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:

Maclaurin Series of sin(x)

And for cosine:

Maclaurin Series of cos(x)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

This post is originally from my blog at xtrp.io.

— Gabriel Romualdo, December 31, 2020

Discussion (5)

pic
Editor guide
Collapse
matthewpersico profile image
Matthew O. Persico

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:

my @nums_to_multiply = (1..$x);
for(@nums_to_multiply){
    $n *= $_;
}
Enter fullscreen mode Exit fullscreen mode

can be replaced with

for(1..$x) {
    $n *= $_;
}
Enter fullscreen mode Exit fullscreen mode

That will save you the memory and the time to copy that series into the array.

A second suggestion:

$i = $i_start;
while($i < $i_start + $ITERATIONS) {
Enter fullscreen mode Exit fullscreen mode

can be replaced with

use strict; # At the top of the code
...
my $i = $i_start;
my $i_end = $i_start + $ITERATIONS
while($i < $i_end) {
Enter fullscreen mode Exit fullscreen mode

No need in recalculating the same number over and over in the loop.

Excellent article.

Collapse
matthewpersico profile image
Matthew O. Persico

Also, by adding use strict; at the top of your Perl code, you will get some errors about undefined vars. Just add mys in the appropriate places and you'll be fine. Always include use strict; in addition to use 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.

Collapse
mjgardner profile image
Mark Gardner

You could do better by using the reduce functions available in each language: Python, JavaScript, Perl

Collapse
shaileshcodes profile image
Shailesh Vasandani

Awesome 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!

Collapse
xtrp profile image
Gabriel Romualdo Author

Thanks! I'm glad you enjoyed the article, and happy new year!
— Gabriel