default parameters
in ye olde times, we wrote default parameters like this
function add (x, y) {
if (x === undefined) x = 0
if (y === undefined) y = 0
return x + y
}
now with ES2015 and later, we can write them like this
function add (x = 0, y = 0) {
return x + y
}
keep things simple
the recursive procedure can be written simply as
const converge = (k = 0) =>
k < 0
? 0
: converge (k - 1) + (Math.pow (-3, -k) / (2 * k + 1))
console.log (converge (1000)) // 0.9068996821171091
console.log (Math.PI / Math.sqrt (12)) // 0.9068996821171089
of course, it helps readability if you abstract sigma
first
const sigma = f => k =>
k < 0
? 0
: f (k) + sigma (f) (k - 1)
// hey, this looks just like the formula you posted
const converge =
sigma (k => Math.pow (-3, -k) / (2 * k + 1))
console.log (converge (1000)) // 0.9068996821171091
console.log (Math.PI / Math.sqrt (12)) // 0.9068996821171089
stack safety
I would like to point out that the stack was never in danger over overflowing – it would take a k
-value of around 10,000 to cause an overflow
console.log (converge (1e4)) // RangeError: Maximum call stack size exceeded
In this case, it doesn't matter, because even a tiny k
-value of 10 already computes the sixth decimal place; a k
-value of 100 computes 14 decimal places
But whatever the case, maybe you have some library that lets you compute decimals with more precision and you want to make converge
stack safe...
const recur = (...args) =>
({ type: recur, args })
const loop = f =>
{
let acc = f ()
while (acc && acc.type === recur)
acc = f (...acc.args)
return acc
}
// now stack-safe
const sigma = f => n =>
loop ((acc = 0, k = n) =>
k < 0
? acc
: recur (acc + f (k), k - 1))
const converge =
sigma (k => Math.pow (-3, -k) / (2 * k + 1))
console.log (converge (1e4)) // 0.9068996821171089 << super precise !!
console.log (Math.PI / Math.sqrt (12)) // 0.9068996821171089