我试图用数值(使用分析公式)计算以下积分序列的值:
I(k,t) = int_0^{N/2-1} u^k e^(-i*u*delta*t) du
其中“i”是虚构单位。对于小k,可以手动计算该积分,但是对于较大的k,更方便的是注意到序列项之间存在迭代关系,该关系可以通过部分的积分得出。这在下面由函数i1实现。
void i1(int N, double t, double delta, double complex ** result){
unsigned int k;
(*result)=(double complex*)malloc(sizeof(double complex)*N);
if(t==0){
for(k=0;k<N;k++){
(*result)[k]=pow(N-2,k+1)/(pow(2,k+1)*(k+1));
}
}
else{
(*result)[0]=2/(delta*t)*sin(delta*(N-2)*t/4)*cexp(-I*(N-2)*t*delta/4);
for(k=1;k<N;k++){
(*result)[k]=I/(delta*t)*(pow(N-2,k)/pow(2,k)*cexp(-I*delta*(N-2)*t/2)-k*(*result)[k-1]);
}
}
}
问题在于,在我的情况下,t非常小(1e-12),delta通常约为1e6。当在N = 4的情况下进行测试时,我注意到k = 3出现了一些奇怪的结果,即结果突然非常大,比它们应该大得多,因为积分的范数总是小于范数的积分,测试结果如下:
I1(0,1.0000e-12)=1.0000000000e+00+-5.0000000000e-07I
Norm=1.0000000000e+00
compare = 1.0000000000e+00
I1(1,1.0000e-12)=5.0000000000e-01+-3.3328895199e-07I
Norm=5.0000000000e-01
compare = 5.0000000000e-01
I1(2,1.0000e-12)=3.3342209601e-01+-2.5013324745e-07I
Norm=3.3342209601e-01
compare = 3.3333333333e-01
I1(3,1.0000e-12)=2.4960025766e-01+-2.6628804517e+02I
Norm=2.6628816215e+02
compare = 2.5000000000e-01
k = 3并不是特别大,我手工计算了积分的值,但是我得到了计算器和分析公式我得到的结果与假想部分的结果相同。我也意识到,如果我改变了术语的顺序,结果就会改变。因此,它似乎是一个精确的问题,因为在迭代过程中有一个非常大但几乎相等的项的减法,并遵循在这个线程上所说的:How to divide tiny double precision numbers correctly without precision errors?,这可能导致小错误被放大。但是,我发现在我的案例中很难看到如何解决这个问题,并且还想知道是否有人可以简要解释为什么会发生这种情况?
答案 0 :(得分:1)
你必须非常小心浮点加法和减法。
假设一个精度为6位的十进制浮点(为了简单起见)。在大型数据中添加/减少一个小数字会丢弃一些甚至全部较小的数字。所以:
5.00000E+9 + 1.45678E+4 is: 5.00000 + 0.000014 E+9 = 5.00001E+9
这是最好的。但是如果你向一个大数字添加一系列小数字,那么你最好先将小数字加在一起,然后将结果添加到大数字中。
减去相似大小的数字是另一种失去精确度的方法。所以:
5.12346E+4 - 5.12345E+4 = 1.00000E-1
现在,这两个数字最多可以是它们的实际值+/-最低有效数字的一半,在这种情况下是0.5E-1 - 这是约+/- 1E-6的相对误差。减法的结果仍然是+/- 0.5E-1(我们不能减少误差!),这是+/- 0.5的相对误差!!!
乘法和除法表现得更好 - 直到你过度/不足。
但是一旦你用加/减法做任何迭代,就继续对自己说(大声):浮点数不是(完全)像实数。