组合数的计算与溢出
,约 ;而定义式分子上的 有 82 位十进制数字,量级在 。中间量比答案本身大出六十多个数量级,这段落差在双精度浮点里无处安放。本页量出四种求值实现各自失真的位置,并给出把 放大到任意规模时仍然可算的模素数路线。
1 · 阶乘定义式的量级落差
JavaScript 的 Number 是 IEEE 754 双精度,有效位 53 位,可精确表示的最大整数 Number.MAX_SAFE_INTEGER 为
,约
。越过这个界,相邻可表示整数之间开始出现空隙,整数加乘不再封闭。
阶乘越界得很早。流行的说法是
起就不再精确,理由是
已超过
。实测把这个界推后了两位:
与
在 Number 里仍与精确值逐位相符,第一个存不下的阶乘是
。原因在于阶乘的二进制表示尾部带着大量零位:
含
这个因子,剔掉 18 个零位后有效部分只剩 48 位;
的零位是 19 个,有效部分升到 56 位,这才装不下。
警示 · 阶乘式的失效方式不止「差几个数」。Number 能表示的最大值约
,
尚在其内,
溢出为 Infinity。此时
按定义式算得 Infinity,而答案只是
;
的分子与分母同为 Infinity,商是 NaN。
2 · 乘除交替的迭代式
避开大阶乘的办法是让乘与除交替进行, 的展开式(见组合 C(n, k))改写成 个因子的连乘:
按这个次序求值,每一步的中间量都是整数,除法从不留余数。
定理 2.1 记 ,则 对 成立。
证明 展开即 ,分子是 个连续整数之积。 个连续整数之积必被 整除,商恰为 。逐步看则有 ,其中 已是整数,而 保证乘出的分子被 整除。∎
取
之后
单调递增,中间量的峰值就是
本身。把 Number 换成 BigInt、把 / 换成整数除法,整条链精确无损,代价是
次大整数乘除。
3 · 只用加法的递推
第三条路径绕开除法:Pascal 递推 只做加法(见 Pascal 三角与二项式定理)。滚动一行数组、每行从高位往低位覆写即可, 次加法、 空间。它不能跳算:求单个 也得把前 行推一遍。
换来的是干净的误差行为。加法在 Number 上只要两个加数与和都不越过
就是精确的,而递推路径上出现的全是
本身,没有比答案更大的量。
4 · 浮点实现的失真边界
三种 Number 实现的首处失真位置各不相同,扫描
的全部
与 BigInt 精确值逐对比对,结果如下:
| 实现 | 首处失真 | 该处精确值 | 是否越过 |
|---|---|---|---|
阶乘式 · Number |
2488589544741300 | 否 | |
迭代式 · Number |
3167295784216200 | 否 | |
Pascal 递推 · Number |
9929472283517787 | 是 | |
迭代式 · BigInt |
无 | 不适用 | 不适用 |
Pascal 递推的失真点与「第一个越过 的组合数」重合: 正是所有 中首个超过 的值。扫描 内全部未越界的 ,递推实现一处不差——它撑到了信息论允许的最后一格。乘除式则不然。
警示 · 本仓库 combinatorics/core/combinatorics.ts 的 comb() 走的是浮点迭代式加 Math.round。它与精确值的第一处分歧在
:精确值
,comb() 给出
,差
,相对误差
。这个精确值只有
,仍在安全整数以内,失真来自中间量的舍入累积而非结果装不下。
范围内共 23 个
落在这类分歧上。本系列既有页面用到的
都在 10 以内,远在这条界线之内;此处记的是适用范围。
5 · 模素数的组合数与 Lucas 定理
许多场合只需要 。 为素数时,费马小定理给出 ,于是 就是 的逆元(modular inverse),除以 改写为乘上 ,全程在 内运算。这条路要求 :否则 含因子 ,模 得零,逆元不存在。
超过 时接上 Lucas 定理。把 与 写成 进制,、,则
每位的 与 都小于 ,逐位取组合数再相乘即可,位数只有 。某一位上 时该位的组合数为零,整个乘积随之归零——这也给出了 整除 的一个判据(Kummer 定理的特例)。
6 · 参考文献
- Binomial coefficient. Wikipedia. 组合数的多种计算方法与整除性质。https://en.wikipedia.org/wiki/Binomial_coefficient
- Lucas's theorem. Wikipedia. Lucas 定理的陈述与 进制证明。https://en.wikipedia.org/wiki/Lucas%27s_theorem
- Fermat's little theorem. Wikipedia. 费马小定理及其在模素数求逆上的用法。https://en.wikipedia.org/wiki/Fermat%27s_little_theorem
- Double-precision floating-point format. Wikipedia. IEEE 754 双精度的 53 位有效位与安全整数范围。https://en.wikipedia.org/wiki/Double-precision_floating-point_format