我认为问题是由于
np.exp(-x)
随着
x
的增加迅速变得非常小,这导致由于有限的数值精度而评估为零。例如,即使对于像
x=10**2
这样小的
x
,
np.exp(-x)
也会评估为
3.72007597602e-44
,而
x
的值大约为
10**3
或更高,则结果为
0
。
我不知道
quad
的具体实现,但它可能执行一些函数的采样以在给定的积分范围内进行积分。对于较大的上限积分,
np.exp(-x)
的大多数样本评估为零,因此低估了积分值。(请注意,在这些情况下,
quad
提供的绝对误差与积分值的数量级相同,这表明后者不可靠。)
避免此问题的一种方法是将积分上限限制为一个值,该值高于数值函数变得非常小(因此对积分值的贡献微不足道)。从您的代码片段中可以看出,
10**4
的值似乎是一个很好的选择,但是
10**2
的值也会导致对积分的准确评估。
避免数值精度问题的另一种方法是使用一个执行
任意精度算术计算的模块,例如
mpmath
。例如,对于
x=10**5
,
mpmath
使用本地
mpmath
指数函数评估
exp(-x)
如下:
import mpmath as mp
print(mp.exp(-10**5))
3.56294956530937e-43430
请注意这个数值的极小。使用标准硬件数值精度(由numpy
使用),这个值会变成0
。
mpmath
提供了一个积分函数(mp.quad
),可以为任意上限值提供准确的积分估计。
import mpmath as mp
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, mp.inf]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**13]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**8]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**5]))
1.0
0.999999650469474
0.999999999996516
0.999999999999997
如果我们将精度提高到50
位小数(标准精度为15
位),我们还可以获得更准确的估计值。
mp.mp.dps = 50;
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, mp.inf]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**13]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**8]))
print(mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**5]))
1.0
0.99999999999999999999999999999999999999999829880262
0.99999999999999999999999999999999999999999999997463
0.99999999999999999999999999999999999999999999999998
通常,获得这种精度的代价是增加计算时间。
P.S .: 不用说,如果您能够在第一时间通过解析法(例如使用Sympy
)评估积分,那么您可以忘记上述所有内容。
mp.quad(lambda x : .5 * mp.exp(-.5 * x), [0, 10**20])
->2.20502636520112e-56
。关键在于,函数的数值积分在没有一些“光滑性”条件的情况下是不可能的——函数在积分区间内不能有太尖锐的“尖峰”。当积分区间非常大时,函数exp(-x/2)
非常“尖锐”,这就导致了问题。 - pv.mp.quad
之前尝试mp.mp.dps = 100
。 - Stelios10**120
。这也会增加计算成本,在这种情况下是不必要的。问题不在于函数值太小而低于浮点范围,而在于当函数按积分区间缩放时,非常尖锐,这会误导积分算法的误差估计。 - pv.mpmath
与scipy
、pandas
和其他流行的包兼容吗? - Denis Korzhenkov