// Guy Fernando (2023).
// Calculates PI using various algorithms.
// Implementation using arbitary precision arithmetic library, decimal.js.
// When calculating pi, decimal.js is faster than big.js.
// For algorithms see: http://mathworld.wolfram.com/PiFormulas.html
importScripts('decimal.min.js');
// Abstract base class for calculating pi.
//
class PiAlgorithm {
// Constructor.
//
constructor(digits, digitsPerIteration) {
this.digits = digits;
this.digitsPerIteration = digitsPerIteration;
this.startTime = 0;
this.endTime = 0;
}
// Factorial that doesn't overflow with large n.
//
factorial(n) {
try {
var i = 2, r = new Decimal(1);
for (; i <= n; r = r.times(i++))
;
}
catch (err) {
console.log(err.message);
}
return r;
}
// Gets the time taken in milliseconds to calculate a single pi digit.
//
getTimePerDigit() {
return ((this.endTime - this.startTime) / this.digits).toFixed(2);
}
}
// Bailey Borwein Plouffe (BBP) method for calculating pi.
//
class BBP extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 1.0);
}
// The Bailey Borwein Plouffe (BBP) pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var M, T1, T2, T3, T4;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multiplier, M = 1 / 16^k
M = Decimal(1).div(Decimal(16).pow(Decimal(k)));
// Term 1, T1 = 4 / (8k + 1)
T1 = Decimal(4).div(Decimal(8).times(Decimal(k)).plus(1));
// Term 2, T2 = 2 / (8k + 4)
T2 = Decimal(2).div(Decimal(8).times(Decimal(k)).plus(4));
// Term 3, T3 = 1 / (8k + 5)
T3 = Decimal(1).div(Decimal(8).times(Decimal(k)).plus(5));
// Term 4, T4 = 1 / (8k + 6)
T4 = Decimal(1).div(Decimal(8).times(Decimal(k)).plus(6));
// Pi partial summation.
pi = pi.plus(M.times(T1.minus(T2).minus(T3).minus(T4)));
}
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Chudnovsky method for calculating pi.
//
class Chudnovsky extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 14.1816474627254776555);
}
// The Chudnovsky pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var C, Mk, Lk, Xk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multinomial term, Mk = (6k)! / (3k)! * (6k)!^3
Mk = Decimal(this.factorial(6 * k)).div(Decimal(this.factorial(3 * k)).times(Decimal(this.factorial(k)).pow(3)));
// Linear term, Lk = 545140134k + 13591409
Lk = Decimal(545140134 * k).plus(13591409);
// Exponential term, Xk = -262537412640768000^k
Xk = Decimal(-262537412640768000).pow(k);
// Pi series partial summation.
pi = pi.plus(Mk.times(Lk).div(Xk));
}
// C = 1 / (426880 * 10005^0.5)
C = Decimal(1).div(Decimal(426880).times(Decimal(10005).sqrt()));
// Multiply by constant and take reciprocal.
pi = Decimal(1).div(C.times(pi));
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Ramanujan method for calculating pi.
//
class Ramanujan extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 8.0);
}
// The Ramanujan pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var C, Mk, Lk, Xk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Multinomial term, Mk = (4k)! / (k!)^4
Mk = Decimal(this.factorial(4 * k)).div(Decimal(this.factorial(k)).pow(4));
// Linear term, Lk = 1103 + 26390k
Lk = Decimal(26390 * k).plus(1103);
// Exponential term, Xk = 396^4k
Xk = Decimal(396).pow(4 * k);
// Pi series partial summation.
pi = pi.plus(Mk.times(Lk).div(Xk));
}
// C = (2 * 2^0.5) / 9801
C = Decimal(2).times(Decimal(2).sqrt()).div(9801);
// Multiply by constant and take reciprocal.
pi = Decimal(1).div(C.times(pi));
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Newton-Euler method for calculating pi.
//
class NewtonEuler extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 0.3);
}
// The Newton-Euler pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 3;
var pi = new Decimal(0);
var Nk, Dk;
var iterations = (this.digits / this.digitsPerIteration) + 1;
for (var k = 0; k < iterations; k++) {
// Numerator term, Nk = 2^k * (k!)^2
Nk = Decimal(2).pow(k).times(Decimal(this.factorial(k)).pow(2));
// Denominator term, Dk = (2k + 1)!
Dk = Decimal(this.factorial((2 * k) + 1));
// Pi series partial summation.
pi = pi.plus(Nk.div(Dk));
}
// Multiply by 2.
pi = pi.times(2);
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Madhava method for calculating pi.
//
class Madhava extends PiAlgorithm {
// Constructor.
//
constructor(digits) {
// The number of digits, and decimal digits the algorithm generates per iteration.
super(digits, 0.4);
}
// The Madhava pi calculation.
//
calculate() {
try {
// Journal start time.
this.startTime = performance.now();
Decimal.precision = this.digits + 2;
var pi = new Decimal(0);
var Nk, Dk;
var iterations = (this.digits / this.digitsPerIteration) + 2;
for (var k = 1; k < iterations; k++) {
// Numerator term, Nk = (-1)^(k+1)
Nk = Decimal(-1).pow(k + 1);
// Denominator term, Dk = (2k - 1) * 3^(k-1)
Dk = Decimal((2 * k) - 1).times(Decimal(3).pow(k - 1));
// Pi series partial summation.
pi = pi.plus(Nk.div(Dk));
}
// Multiply by 12^0.5.
pi = pi.times(Decimal(12).sqrt());
// Set significant digits.
pi = pi.toSD(this.digits);
// Journal end time.
this.endTime = performance.now();
return pi;
}
catch (err) {
console.log(err.message);
return 0;
}
}
}
// Web worker message receiver.
//
self.onmessage = function (event) {
// Reconstitute json object containing parameters.
var jsonParameters = JSON.parse(event.data);
// Extract parameters.
var digits = Number(jsonParameters.Digits);
var algorithm = jsonParameters.Algorithm;
// Instantiate required algorithm.
var pi;
switch (algorithm) {
case "BBP":
pi = new BBP(digits);
break;
case "Chudnovsky":
pi = new Chudnovsky(digits);
break;
case "Ramanujan":
pi = new Ramanujan(digits);
break;
case "Newton-Euler":
pi = new NewtonEuler(digits);
break;
case "Madhava":
pi = new Madhava(digits);
break;
}
// Perform pi calculation.
const piValue = pi.calculate();
const timePerDigit = pi.getTimePerDigit();
// Send json message containing results to main thread.
var jsonResult = { Algorithm: algorithm, Digits: digits, PiValue: piValue.toString(), TimePerDigit: timePerDigit.toString() };
self.postMessage(JSON.stringify(jsonResult));
};