技术细节

这个软件大部分是用 C 写的, 里边内嵌了一些 32 位 (Intel) 汇编. 在关键的地方用汇编替代 C 语句对于速度的提高可以达到 500%.

因为验证收敛性, 寻找滑翔记录和最高点记录需要的运算量比寻找步数分类记录所需的少, 所以我们使用两种算法分别来达到这个目的.

验证收敛性算法 

用于验证收敛性, 寻找滑翔记录和最高点记录的算法使用了一种筛法, 具体就是只有在若干次变换以内不会变得比原来还小的数才会被检查. 这个方法的效果十分巨大. 实际上, 用大小为 65536 (216) 的筛的话, 每 65536 个数中只有 1720 个需要被检验. 现在我们使用的是大小为 232 的筛, 这就意味着超过 99.2 % 的数可以跳过实际的检查. 除此以外, 这些数字还要被检查是否会掉进另一个比它小的数的路径中. 所有模 9 余 2, 4, 5, 8 的数都会掉进另一个比它小的数的路径中, 所以也都可以略去. 这样 44.4 % 的通过筛的数又被除去了. 为了使代码更快, 我们还使用了一种 "交错式" 的技术, 在每个模 232 的同余类中一次处理 212 个数字. 这就意味着上文所说的筛可以每次使用都重新计算, 而不需要储存起来. 这也意味着我们可以同时计算这么多数的前 32 步. 这使计算时间减少了大约 50%.

寻找步数分类记录的算法

在寻找高归一步数的算法当中, 我们用了几个方法来加快计算. 最主要的方法是使用一个筛来筛选去除很多不需要计算的数. 跟上面的算法不同的是, 我们这次关注的是路径会归并在一起的那些数而不是会很快减少的那些数. 如果两个数的路径会归并在一起, 那么显然较大的一个数不会是一个步数分类记录. 现在我们使用的筛大小是 218, 这大约筛去了占总数 73% 的不必要计算的数字.
例: 所有形如 8k+4 或者 8k+5 的数在 3 步内都会到达 6k+4, 所以显然形如 8k+5 的数不会是一个步数分类记录 (5 本身是唯一的例外). 所以我们不必检查所有形如 8k+5 的数, 在筛中我们就筛去所有这些数 (具体的就是在筛中把它们标记为 0).

所有偶数和模 2 余 3 的数也都会被跳过, 因为这些数中潜在的步数分类记录可以由之前的记录简单地推出. 将以上这几个想法综合起来, 我们可以筛掉超过 90% 以上的数, 这样就使速度快了 10 倍.

我们在算法中也使用了一项 "截停" 的技术, 在某些点上判断当前计算的数是否有可能有一个较高的归一步数, 若然则继续计算, 若不然就终止这个数的计算. 在绝大部分情况下, 在数字只走了一小段以后就可以把它截停下来了. 为了说明这种技术确实可行, 我们来看一个例子. 假设有两个连续的归一步数记录 N1 和 N2, 它们的归一步数分别是 D1 和 D2. 一旦一个数字在变换过程中掉到 N2 以下的话, 它的归一步数不可能超过 D1 + 当前迭代步数. 如果这个和小于一个给定的界限 (比如说最小的未知步数分类记录的步数) 的话, 这个数就可以被截停了.
实际上截停点使用的归一步数记录显然依赖于实际的数字, 而且实际上为了优化, 不同的区间需要不同的截停点. 在现在计算的这个区间 (大约是 254 附近) 中, 我们用的是归一步数分别为 1820, 1549, 1408, 1307 的归一步数记录, 还有 32 位界限和 16 位界限. 后面两个界限是为了保证在后面的计算中储存中间结果所需要的二进制位数分别不超过 64 和 32 位. 这个方法的有效程度很大程度上依赖于数的大小, 不过在通常情况下这可以把速度提高 10 倍.

最后, 我们用一个表来储存较小数的归一步数, 以免过于频繁地去计算这些小数目的归一步数而浪费时间. 在表中储存了 2048-4095 的归一步数. 一旦有一个数掉到 4096 以下, 我们只需要在表中查一下就知道剩下多少步了.

回到 3x+1 主页面.