我将算法追溯到 1995 年 Stanley Rabinowitz and Stan Wagon 的一篇论文。挺有意思的。
先介绍一下背景。从e的普通十进制表示开始:
e = 2.718281828...
这可以表示为如下的无限和:
e = 2 + 1⁄10(7 + 1⁄10(1 + 1⁄10(8 + 1⁄10(2 + 1⁄10( 8 + 1⁄10(1 ...
显然这不是一个特别有用的表示;我们只是将相同的 e 数字包裹在一个复杂的表达式中。
但是看看当我们用自然数的倒数替换这些 1⁄10 因子时会发生什么:
e = 2 + 1⁄2(1 + 1⁄3(1 + 1⁄4(1 + 1⁄5(1 + 1⁄6( 1 + 1⁄7(1 ...
这种所谓的 mixed-radix 表示为我们提供了一个序列,该序列由数字 2 和一个重复的 1 序列组成。很容易看出为什么会这样。当我们扩展括号时,我们最终得到了著名的 e 泰勒级数:
e = 1 + 1 + 1/2! + 1/3! + 1/4! + 1/5! + 1/6! + 1/7! + ...
那么这个算法是如何工作的呢?好吧,我们首先用混合基数 (0; 2; 1; 1; 1; 1; 1; ...) 填充一个数组。为了生成每个连续的数字,我们只需将该数字乘以 10 并吐出最左边的数字。*
但是由于数字以混合基数形式表示,我们必须在每个数字处使用不同的基数。为此,我们从右到左工作,将第 n 位乘以 10,并将其替换为模 n 的结果值。如果结果大于或等于 n,我们将值 x/n 移动到左边的下一位。 (除以 n 将基数从 1/n! 更改为 1/(n-1)!,这就是我们想要的)。这实际上是内部循环的作用:
while (--n) {
a[n] = x % n;
x = 10 * a[n-1] + x/n;
}
这里,x在程序开始时被初始化为零,而数组开始处的初始0确保每次内循环结束时它都被重置为零。结果,随着程序的运行,数组将逐渐从右侧填充零。这就是为什么 n 可以在外循环的每一步都用递减的值 N 来初始化。
可能包含数组末尾的额外 9 位数字以防止舍入错误。运行此代码时,x 达到最大值 89671,这意味着商将跨越多个数字。
注意事项:
这是spigot algorithm 的一种类型,因为它使用简单的整数运算输出e 的连续数字。
Rabinowitz 和 Wagon 在他们的论文中指出,this algorithm was actually invented 50 years ago by A.H.J. Sale
* 除了第一次迭代输出两位数(“27”)