I recently faced a coding challenge where the result had to be a List class that could handle infinite-length lists. As part of the solution, the List class had to have a PI()
method that should return a calculation of π. Long story short, I decided to handle infinity with generator functions and used this same concept to implement the PI()
method generator.
Here is an overview of the involved methods to calculate π:
class List {
constructor(generator, min = 0, max = Infinity) { /* ... */}
// Implementation of other list methods...
get(n) { /* Obtains a value based on the number provided */ }
static arctan(x) { /* Generator for Arctan series */ }
static get PI() { /* Generator for PI calculation */ }
}
This is an overview of how the implementation was built, we can appreciate:
constructor
method is the one receiving the generator function and storing it for later use.arctan
method receives a parameter named x
that we will get in detail later on.PI
method is of type get
, which means we can directly invoke it and reference its values based on the generator function.Moving forward, we are going to quickly analyze a few steps before the construction of the arctan(x)
method.
The formula I used to build the arctan(x)
method refers to this concept of inverse trigonometry functions
being arctan one of the most important ones. Since arctan has many different uses, I had to focus only on those used for calculating π. For now, keep in mind the following formula:
const PI = 4 * ( arctan(1/2) + arctan(1/3) );
This is a Machin-like formula to calculate the approximate value of π, more specifically it is Euler's solution that implements the Taylor series, a representation of the following infinite sum:
Or if we translate it to code:
0 + x^1/1 - x^3/3 + x^5/5 - x^7/7 + ...Continues infinitely
So, here is the implementation of the arctan(x)
method I ended up with:
static arctan(x) {
function* generator() {
let sum = 0;
for(let i = 0; true; i++) {
sum += (Math.pow(-1, i) * Math.pow(x, 2 * i + 1)) / (2 * i);
yield sum;
}
}
return new List(generator);
}
For a better understanding, I will explain the formula in two parts:
Since I decided to have a variable that works as a counter (i
) I was able to determine the current operation sign between +
and -
with the following code:
Math.pow(-1, i)
// Examples
console.log(Math.pow(-1, 0)); // 1
console.log(Math.pow(-1, 1)); // -1
console.log(Math.pow(-1, 2)); // 1
console.log(Math.pow(-1, 3)); // -1
And since the formula uses the inline sum operator +=
it automatically determines the sign that should be used in the current iteration.
The representation of the x^1/1
in code is the following fragment of code:
Math.pow(x, 2 * i + 1) / (2 * i);
And since it is a sum of all those elements, the result is always being saved to the sum
variable:
sum += (Math.pow(-1, i) * Math.pow(x, 2 * i + 1)) / (2 * i);
With the previous formula already working, implementing the PI()
method was simpler because, as mentioned previously, I used Euler's solution to calculate π. (The formula I suggested to keep in mind)
static get PI() {
function* generator() {
let counter = 0;
let pi = 0;
yield pi;
// This formula:
const [ arc1_2, arc1_3 ] = [ List.arctan(1 / 2), List.arctan(1 / 3) ];
while(true) {
// PI = 4 * ( arctan(1/2) + arctan(1/3) )
// Getting the length based on current iteration of PI
pi = 4 * (arc1_2.get(counter) + arc1_3.get(counter));
yield pi;
counter++;
}
}
return new List(generator);
}
This was the hardest part because test cases were also approximation assertions. But as far as the entire List class implementation was done it can be used as:
console.log(List.PI.get(1)); // 3.333333333333333
console.log(List.PI.get(5)); // 3.1417411974336886
console.log(List.PI.get(12)); // 3.1415926497167876
console.log(List.PI.get(100)); // 3.1415926535897922
The higher the x
value, the more precise the result will be.