在19世纪,一位法国钟表匠阿喀琉斯·布罗科特(Achilles Brocot)在寻找制作齿轮的最佳方法时,意外地发现了一种对无理数进行有理近似的最佳方法。尽管现代处理器大多配备了浮点单元,但在某些情况下,使用整数进行计算仍然具有优势。例如,可以从Wikipedia上找到π的近似值355/113,精确到小数点后7位。但是,如果算法需要像ln(2)或φ这样的值,又该怎么办呢?
可以通过斯特恩-布罗科特(Stern-Brocot)树结构找到这些答案。这棵树的前几级如下图所示:
如果只对编程细节感兴趣,可以跳过这部分,直接进入应用部分。
有理数是可以表示为分数p/q的数。由于许多分数可以表示相同的有理数,只选择p和q没有公因数的分数。例如,2/3和4/6表示相同的数,但只选择前者。如果在两个轴上排列整数,可以将有理数想象成从原点到p/q点的线的斜率。下图显示了“2/3”的表示。
现在需要定义“中介数”的概念:两个有理数m/n和m'/n'的中介数是有理数(m+m')/(n+n')。下图表示了数字1/2和1/3及其中介数2/5。
将这些数字视为向量加法,可以看出两个数的中介数位于它们之间。用数学术语来说,如果m/n < m'/n',那么m/n < (m+m')/(n+n') < m'/n'。
现在可以开始构建斯特恩-布罗科特树。从数字0/1和1/0开始。1/0在技术上不是一个分数,但将其视为“无穷大”的表示。
在树的每一级,添加前一级数字的中介数。在第二级,放置这些数字的中介数(0+1)/(1+0) = 1/1。在下一级,放置每个数字的中介数:(0+1)/(1+1) = 1/2和(1+1)/(1+0) = 2/1。
下一张图片显示了树的连续级别如何扩展以覆盖有理数的网格。灰色的点是p和q有公因数的地方。
已经看到,树在任何级别上都有前一级值的中介数,中介数的值位于其祖先值之间。在这样的树中,用以下算法搜索具有足够精度ε的数字N的近似值:
Let current node T be the root of the tree.
If |N-T| < ε exit and the answer is T.
If N < T, replace T with the left child of T, otherwise replace T with the right child of T.
Proceed to step 2.
这个算法,或者更确切地说,斯特恩-布罗科特树的性质,保证了找到的近似值p/q是“最好”的,因为任何其他具有相同精度的p'/q'近似值都会有p'>p和q'>q。
sba程序计算给定精度下的最佳有理近似值。命令行是:
sba
这里有一个计算π7位小数近似值的例子:
没什么花哨的!每个树节点由一个sbnode结构表示,包含p和q值,左和右子节点以及指向父节点的指针。
struct sbnode {
int p, q;
struct sbnode *left, *right, *parent;
sbnode (sbnode *parent_) {
p = q = 0;
left = right = 0;
parent = parent_;
}
~sbnode () {
if(left)
delete left;
if(right)
delete right;
}
}
构造函数和析构函数负责初始化节点和清理结束时。程序实现了前面描述的算法。然而,它并没有事先创建树。相反,只有在需要时才创建子节点。
为了显示质因数分解,使用埃拉托斯特尼筛法创建一个质数表。这不是寻找质因数的最快方法,但对于处理的小数来说,这是完全足够的。
阅读这个故事后,可能会想知道为什么钟表匠对这些树感兴趣。如果两个齿轮,一个有p个齿,另一个有q个齿连接在一起,它们的旋转比(齿轮比)是p/q。
齿轮可以像下面的例子中的双齿轮一样组合:
在这种情况下,如果第一对(红/蓝)的齿轮比是p1/q1,第二对(黄/绿)的齿轮比是p2/q2,那么组合齿轮比(红/绿)是p1/q1 * p2/q2。
作为一个钟表匠过去遇到的问题的例子,18世纪钟表匠查尔斯-埃蒂安-路易·卡穆斯(Charles-Étienne-Louis Camus)提出了以下问题:“找到机器的齿轮和齿轮的齿数……被一个齿轮驱动,放在小时轮上,将使一个齿轮在平均一年,假设由365天,5小时,49分钟组成,完成一次旋转。”如果将一切都转换为分钟,问题需要设计一个齿轮传动比为720/525949(小时轮在12小时=720分钟内完成一次旋转,根据卡穆斯,平均热带年有525949分钟)。
建造一个有525949个齿的齿轮是很难想象的,而且在这种情况下制作复合齿轮受到525949是一个质数的事实的阻碍。卡穆斯不得不找到一个接近所需比率的组合,但数字可以很好地分解。
小sba实用程序提供了一个答案:
~sba -f 0.0013689540240594 10
找到0.0013689540的近似值,精确到10位小数
...
当前近似97/70857 = 0.0013690 (err=-3.49e-10)
97是质数
70857的因数 = 3^2*7873
当前近似130/94963 = 0.0013690 (err=-2.00e-10)
130的因数 = 2*5*13
94963的因数 = 11*89*97
当前近似163/119069 = 0.0013690 (err=-1.12e-10)
163是质数
119069是质数
找到分数196/143175 = 0.00136895408
Error= -5.31e-11
196的因数 = 2^2*7^2
143175的因数 = 3*5^2*23*83
这个数字可以分解为4/25 * 7/69 * 7/83。卡穆斯花了20页的艰苦工作才得到相同的结果。
1901年发现的安提基特拉机械是一个希腊天文计算器,可以追溯到公元前二世纪或一世纪。自其发现以来,它挑战了许多研究人员找到这个技术奇迹的内部工作原理。
最近,伦敦大学学院(UCL)的一个团队发表了该机械的完整重建。为了找到齿轮比,作者提出了一个名为“帕尔门尼德命题”的过程:
已经开发了一种关于如何发现金星和土星周期的新理论,并将其应用于恢复缺失的行星周期。柏拉图(公元前五世纪-公元前四世纪)的对话以哲学家帕尔门尼德(公元前六世纪-公元前五世纪)的名字命名。这描述了帕尔门尼德命题:
In approximating θ, suppose rationals, p/q and r/s, satisfy p/q < θ < r/s. Then (p + r)/(q + s) is a new estimate between p/q and r/s:
If it is an underestimate, it is a better underestimate than p/q. If it is an overestimate, it is a better overestimate than r/s.
这个描述完全符合中介数的定义和斯特恩-布罗科特树中的搜索算法。
使用帕尔门尼德命题,作者描述了寻找金星齿轮的过程。它必须是一个接近720/1151的数字,并且有一个方便的质因数分解。下面是sba实用程序输出的一个片段:
最好的数字是289/462。
继续,作者为所有行星建立了不同的齿轮比,并创建了机械的完整重建。
发现从柏拉图的对话,到希腊天文学家,18世纪钟表匠和当代浮点近似值,追溯斯特恩-布罗科特树的结构非常有趣。