这个Math系列写到这里,我已经聊过不少看似抽象、上手才发现特别“稳”的数学对象。第3篇我打算换一个组合:一个对数积分,一个非线性递推。前者属于分析学,背后站着素数定理;后者属于离散数学,站在Laurent现象和Somos序列的交叉口。表面上它们八竿子打不着,但我实际推导和写代码验证时,发现它们共同流露出一种气质:公式里明明写着“危险”,收敛性要打问号,整除性也完全没有保证,可结果却稳得离谱。对数积分在x=1处带着对数奇点,却成了素数分布最精准的宏观估计之一;Somos-4这类递推每一步都要做除法,分母却永远刚好整除,跑几十项下来没有一处破损。这篇就把两个主题分别讲透,顺便把我实际操作时踩过的坑、验证过的数值细节全部铺开,适合对数学推导和编程验证都感兴趣的人。
1. 对数积分到底在对谁积分:定义与那个躲不开的奇点
1.1 柯西主值:数学处理发散的标准姿势
对数积分的标准定义是
[
\operatorname{li}(x)=\int_0^x\frac{dt}{\ln t}.
]
问题马上来了:当x>1时,积分路径会经过t=1,在那一瞬间(\ln t=0),被积函数无限增大,反常积分并不按普通意义收敛。所以需要柯西主值来处理:
[
\operatorname{li}(x)=\lim_{\varepsilon\to0^+}\left(\int_0^{1-\varepsilon}\frac{dt}{\ln t}+\int_{1+\varepsilon}^x\frac{dt}{\ln t}\right).
]
这个极限的思想可以这样理解:奇点两侧的积分一个往负无穷跑,一个往正无穷跑,而且发散速度都是对数级别,正好互相抵消,于是留下一个确定的有限值。这里有个容易让人困惑的点:主值积分的意义不是把奇点“修好”,而是规定了一种对称的取法。用微积分的语言说,我们接受“两侧分别发散”这个事实,同时通过对称截断让它们的净效应为零。这种处理方式在调和分析里很常见,你以后遇到形如(\int_{-a}^{a}1/x,dx)的积分也会用同一套路。
如果只在数值上关心结果,你可以直接用主值公式写一个数值积分程序,但我劝你不要这么做。这个问题在(t=1)附近被积函数变化太快,普通的自适应积分算法容易判断失误,你得额外复杂化代码去处理主值截断,计算效率和精度都不理想。
1.2 从 Ei(ln x) 的角度理解 li(x)
对数积分之所以研究起来方便,是因为它有一个非常漂亮的恒等式:
[
\operatorname{li}(x)=\operatorname{Ei}(\ln x).
]
其中指数积分(\operatorname{Ei}(z))的定义是
[
\operatorname{Ei}(z)=\operatorname{P.V.}\int_{-\infty}^{z}\frac{e^u}{u},du.
]
这个等式做一次换元就能推出来。令(t=e^u),那么(dt/\ln t=e^u du/u),积分区间从(0)到(x)被映射到从(-\infty)到(\ln x)。于是整个对数积分就变成指数积分在实数轴上的取值,奇点也从(t=1)被搬到了(u=0)。这个变换不是单纯的操作技巧,它把问题归约到一类研究得非常透彻的特殊函数上。现实中,几乎所有数学软件计算(\operatorname{li}(x))都是先算(\ln x)再调指数积分函数。
在实际工程里,我推荐直接用指数积分来算:
python复制import numpy as np
from scipy.special import expi
def li(x):
# 只在 x 不等于 1 时调用
return expi(np.log(x))
这行代码看起来简单,但它能处理绝大多数你需要的点值。注意这个实现没有对x=1做特殊判断,因为(x=1)时(\ln x=0),而(\operatorname{Ei}(0))发散向负无穷。在写数值库接口时,最好单独把这个点排除掉。
1.3 渐近展开:大 x 下的快速估计与失效边界
(\operatorname{li}(x))的渐近展开是
[
\operatorname{li}(x)\sim\frac{x}{\ln x}\left(1+\frac{1}{\ln x}+\frac{2!}{(\ln x)^2}+\frac{3!}{(\ln x)^3}+\cdots\right).
]
细看这个级数,你会发现它每一项里的阶乘越来越大,最终会压倒((\ln x)^k)的幂。也就是说,这是个渐近展开,不是普通意义上的收敛级数。固定的x下,项会先下降、再上升,最好的近似点在中间的某一项附近截断。很多人第一次接触会误以为“项越多越准”,实际恰恰相反。
举一个我经常在演示里用的例子:x=10时,(\ln x\approx2.3026),前两项给出的近似是
[
\frac{10}{2.3026}\left(1+\frac{1}{2.3026}\right)\approx4.343+1.886=6.229.
]
真值(\operatorname{li}(10)\approx6.165),这个近似已经很接近。如果你把第三项、第四项继续加进去,值反而会冲到7.8、9.9,毫不客气地偏离真值。
这就导出一个实用规则:渐近展开适合大参数,不适合小参数。当你处理(x=10^{20})甚至(10^{100})这种量级时,(\ln x)足够大,前几项就能给出令人满意的结果;但如果你拿着这个公式去算(x=10),就会踩坑。更可靠的通用做法是直接用(\operatorname{Ei}(\ln x))的数值实现。理解这个边界,比背下展开式本身更重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. li(x) 与素数计数:为什么它比 x/ln x 更聪明
2.1 万能的启发式:素数密度 1/ln t
素数定理通常写成(\pi(x)\sim x/\ln x),但从实际用途看,(\operatorname{li}(x))或者更常见的(\operatorname{Li}(x)=\int_2^x dt/\ln t)才是更精细的估计。核心直觉是:在t附近,整数是素数的概率大约为(1/\ln t)。这个密度不是常数,它随着t变大缓慢下降。于是从2累积到x,自然得到
[
\pi(x)\approx\int_2^x\frac{dt}{\ln t}=\operatorname{Li}(x).
]
这个启发式推导比直接背(\pi(x)\sim x/\ln x)有意义得多。(x/\ln x)其实是把密度(1/\ln t)粗暴地替换成端点处的(1/\ln x),中间的信息全部丢掉了。而(\operatorname{Li}(x))保留了每个局部位置的密度信息,所以它对素数计数的拟合明显更好。这就像估算一整天的人流量:只看最后时刻的瞬时人流和按每小时人流积分,后者当然更可靠。
2.2 数值对比:π(x)、Li(x)、x/ln x 的真实差距
最好还是让数字说话。下面这张表是我用Python验证时常用的对比数据:
| x | π(x) | Li(x) | x/ln x |
|---|---|---|---|
| 10 | 4 | 6.17 | 4.34 |
| 100 | 25 | 30.13 | 21.71 |
| 1000 | 168 | 177.61 | 144.76 |
| 10^4 | 1229 | 1246.14 | 1085.74 |
| 10^5 | 9592 | 9628.76 | 8685.89 |
| 10^6 | 78498 | 78627.55 | 72382.41 |
可以看到,(\operatorname{Li}(x))几乎总是压着(\pi(x)),误差在百分之几以内;而(x/\ln x)则系统性偏低,在(x=100)时低了约百分之十三。如果你想快速估算素数个数,我会建议直接用(\operatorname{Li}(x))而不是(x/\ln x)。这个结论不是新东西,但很多初学数论的读者仍习惯用(x/\ln x)做粗糙估算,其实少两步积分就能换来明显更准的数字。
使用对称数论库验证的代码可以这样写:
python复制import math
import sympy as sp
from scipy.special import expi
def Li(x):
# 从 2 积分到 x,等价于 li(x) - li(2)
return expi(math.log(x)) - expi(math.log(2))
for n in [100, 1000, 10000, 100000]:
print(n, sp.primepi(n), float(Li(n)), n/math.log(n))
其中sp.primepi(n)会精确计算不超过n的素数个数。跑一次你就明白:(\operatorname{Li}(x))的偏差不是“碰巧比较小”,而是结构性地贴合。
2.3 Skewes 数与“误差变号”的漫长等待
上面那张表给你一个印象:(\operatorname{Li}(x))几乎总比(\pi(x))大一点。数学史上很长一段时间,人们都猜测(\pi(x)<\operatorname{Li}(x))对所有x都成立。这个猜想比想象中顽固,数值验证到了极大的范围都没有找到反例。结果到了1933年,Skewes证明了:如果黎曼假设成立,那么存在某个x,使得(\pi(x)>\operatorname{Li}(x)),也就是误差符号反转,而且这个反例发生的位置比当时所有人预期的都大得多,大约在(10^{10^{10^{34}}})这个量级以下。
我提这个例子的目的,不是让你追求复现那个巨大的反例,而是想说明一个方法论层面的教训:数值遍历能给你很强的心理暗示,但你观察不到的东西不等于不存在。(\operatorname{Li}(x))和(\pi(x))的偏差问题,是“数值实验会骗人”的经典案例。我们在做计算验证时,应该区分“数值上没发现反例”和“证明了结论成立”。这也是为什么数论里那么多工具,严谨证明仍然不可替代。对数积分本身没有错,它是素数分布最可靠的宏观标尺之一;容易出问题的是我们对它的直觉。
