Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/126.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 用指数函数求乘法_C++_Numerical Methods - Fatal编程技术网

C++ 用指数函数求乘法

C++ 用指数函数求乘法,c++,numerical-methods,C++,Numerical Methods,我试图想出一个好的方法来评估下面的函数 double foo(std::vector<double> const& x, double c = 0.95) { auto N = x.size(); // Small power of 2 such as 512 or 1024 double sum = 0; for (auto i = 0; i != N; ++i) { sum += (x[i] * pow(c, double(i)/N));

我试图想出一个好的方法来评估下面的函数

double foo(std::vector<double> const& x, double c = 0.95)
{
   auto N = x.size(); // Small power of 2 such as 512 or 1024
   double sum = 0;
   for (auto i = 0; i != N; ++i) {
     sum += (x[i] * pow(c, double(i)/N));
   }
   return sum;
}
我对这种幼稚实现的两个主要关注点是性能和准确性。因此,我怀疑最微不足道的改进是颠倒循环顺序:对于自动I=N-1;我!=-1; -我把-1包起来,这没关系。这通过首先添加较小的术语来提高准确性

虽然这有助于提高精度,但它保留了pow的性能问题。在数值上,powc,doublei/N是powc,i-1/N*powc,1/N。后者是一个常数。所以理论上我们可以用重复乘法来代替pow。虽然对性能有好处,但这会损害准确性-错误会累积

我怀疑这里隐藏着一个更好的算法。例如,N是2的幂这一事实意味着有一个中间项x[N/2]乘以sqrtc。这暗示了一个递归解决方案

在某种程度上相关的数值观察中,这看起来像是信号与指数相乘,所以我自然认为:FFT,琐碎卷积=移位,IFFT,但这似乎在精度或性能方面没有真正的好处

那么,这是一个已知解决方案的众所周知的问题吗

如果N是2的幂,可以使用几何平均值替换幂的计算

a^(i+j)/2 = √(a^i.a^j)
并从c^N/N.c^0/N递归细分。使用前序递归,可以确保通过增加权重进行累加

无论如何,sqrt对pow的加速可能是微不足道的

您还可以在某个级别停止递归,然后线性继续,仅使用乘积。

如果N是2的幂,您可以使用几何平均值替换幂的计算

a^(i+j)/2 = √(a^i.a^j)
并从c^N/N.c^0/N递归细分。使用前序递归,可以确保通过增加权重进行累加

无论如何,sqrt对pow的加速可能是微不足道的


您还可以在某个级别停止递归,然后线性继续,只使用乘积。

您可以将powc,1./N的重复乘法与一些显式pow调用混合使用。也就是说,大约每第16次迭代做一个真实的pow,然后继续乘法。这将以可忽略不计的精度成本产生巨大的性能优势


根据c的变化程度,您甚至可以预先计算所有pow调用,并用查找替换它们,或者仅使用上述方法所需的调用=较小的查找表=更好的缓存。

您可以将powc的重复乘法,1./N与一些显式pow调用混合使用。也就是说,大约每第16次迭代做一个真实的pow,然后继续乘法。这将以可忽略不计的精度成本产生巨大的性能优势


根据c的变化程度,您甚至可以预计算并使用查找替换所有pow调用,或者仅使用上述方法所需的调用=较小的查找表=更好的缓存。

Yves的回答启发了我

似乎最好的方法不是直接计算powc,1.0/N,而是间接计算:

cc[0]=c;cc[1]=sqrtcc[0],cc[2]=sqrtcc[1],。。。cc[logN]=sqrtcc[logN-1]

或者是二进制的

cc[0]=c,cc[1]=c^0.1,cc[2]=c^0.01,cc[3]=c^0.001

现在如果我们需要x[0b100100]*c^0.100100,我们可以计算为x[0b100100]*c^0.1*c^0.0001。我不需要像geza建议的那样预先计算一张N大小的桌子。一个logN大小的表可能就足够了,可以通过反复求平方根来创建它

[编辑] 正如在另一个答案的评论帖子中指出的,两两相加在控制错误方面非常有效。它恰好与这个答案结合得非常好

我们从观察我们求和开始

x[0] * c^0.0000000
x[1] * c^0.0000001
x[2] * c^0.0000010
x[3] * c^0.0000011
...
所以,我们运行logN迭代。在迭代1中,我们添加N/2对x[i]+x[i+1]*c^0.000001,并将结果存储在x[i/2]中。在迭代2中,我们添加对x[i]+x[i+1]*c^0.000010,等等。与正常两两求和的主要区别在于,这是每一步的乘法和加法


现在我们看到,在每次迭代中,我们使用相同的乘数powc,2^i/N,这意味着我们只需要计算logN乘数。它的缓存效率也很高,因为我们只进行连续内存访问。它还允许简单的SIMD并行化,特别是当您有FMA指令时。

Yves的回答启发了我

似乎最好的方法不是直接计算powc,1.0/N,而是间接计算:

cc[0]=c;cc[1]=sqrtcc[0],cc[2]=sqrtcc[1],。。。cc[logN]=sqrtcc[logN-1]

或者是二进制的

cc[0]=c,cc[1]=c^0.1,cc[2]=c^0.01,cc[3]=c^0.001

现在如果我们需要x[0b100100]*c^0.100100,我们可以计算为x[0b100100]*c^0.1*c^0.0001。我不需要像geza建议的那样预先计算一张N大小的桌子。一个logN大小的表可能就足够了,可以通过反复求平方根来创建它

[编辑] 正如在anoth的评论帖子中指出的 呃,答案是,两两相加在控制错误方面非常有效。它恰好与这个答案结合得非常好

我们从观察我们求和开始

x[0] * c^0.0000000
x[1] * c^0.0000001
x[2] * c^0.0000010
x[3] * c^0.0000011
...
所以,我们运行logN迭代。在迭代1中,我们添加N/2对x[i]+x[i+1]*c^0.000001,并将结果存储在x[i/2]中。在迭代2中,我们添加对x[i]+x[i+1]*c^0.000010,等等。与正常两两求和的主要区别在于,这是每一步的乘法和加法


现在我们看到,在每次迭代中,我们使用相同的乘数powc,2^i/N,这意味着我们只需要计算logN乘数。它的缓存效率也很高,因为我们只进行连续内存访问。它还允许简单的SIMD并行化,特别是当您有FMA指令时。

该任务是多项式计算。操作次数最少的单一评估方法是Horner方案。通常,较低的操作计数将减少浮点噪声的累积

由于示例值c=0.95接近1,因此任何根都将更接近1,从而失去准确性。通过直接计算差为1,z=1-c^1/n,通过

现在你必须计算多项式

sum of x[i] * (1-z)^i
这可以通过仔细修改Horner方案来实现。而不是

for(i=N; i-->0; ) {
  res = res*(1-z)+x[i]
}
使用

for(i=N; i-->0; ) {
  res = (res+x[i])-res*z
}
这在数学上是等价的,但在1-z中的数字丢失发生得越晚越好,而无需使用更复杂的方法,如双精度加法

在试验中,这两种与目的相反的方法给出了几乎相同的结果,通过将结果分为c=1、z=0和z的倍数,可以观察到显著的改善,如中所示

显示这种改进的测试用例是针对序列的系数

f(u) = (1-u/N)^(N-2)*(1-u)
其中,对于N=1000,评估结果为

   c               z=1-c^(1/N)             f(1-z)         diff for 1st proc     diff for 3rd proc

  0.950000     0.000051291978909      0.000018898570629  1.33289104579937e-17  4.43845264361253e-19
  0.951000     0.000050239954368      0.000018510931892  1.23765066121009e-16  -9.24959978401696e-19
  0.952000     0.000049189034371      0.000018123700958  1.67678642238461e-17  -5.38712954453735e-19
  0.953000     0.000048139216599      0.000017736876972  -2.86635949350855e-17  -2.37169225231204e-19
...
  0.994000     0.000006018054217      0.000002217256601  1.31645860662263e-17  1.15619997300212e-19
  0.995000     0.000005012529261      0.000001846785028  -4.15668713370839e-17  -3.5363625547867e-20
  0.996000     0.000004008013365      0.000001476685973  8.48811716443534e-17    8.470329472543e-22
  0.997000     0.000003004504507      0.000001106958687  1.44711343873661e-17  -2.92226366802734e-20
  0.998000     0.000002002000667      0.000000737602425   5.6734266807093e-18  -6.56450534122083e-21
  0.999000     0.000001000499833      0.000000368616443  -3.72557383333555e-17  1.47701370177469e-20

这项任务是多项式评估。操作次数最少的单一评估方法是Horner方案。通常,较低的操作计数将减少浮点噪声的累积

由于示例值c=0.95接近1,因此任何根都将更接近1,从而失去准确性。通过直接计算差为1,z=1-c^1/n,通过

现在你必须计算多项式

sum of x[i] * (1-z)^i
这可以通过仔细修改Horner方案来实现。而不是

for(i=N; i-->0; ) {
  res = res*(1-z)+x[i]
}
使用

for(i=N; i-->0; ) {
  res = (res+x[i])-res*z
}
这在数学上是等价的,但在1-z中的数字丢失发生得越晚越好,而无需使用更复杂的方法,如双精度加法

在试验中,这两种与目的相反的方法给出了几乎相同的结果,通过将结果分为c=1、z=0和z的倍数,可以观察到显著的改善,如中所示

显示这种改进的测试用例是针对序列的系数

f(u) = (1-u/N)^(N-2)*(1-u)
其中,对于N=1000,评估结果为

   c               z=1-c^(1/N)             f(1-z)         diff for 1st proc     diff for 3rd proc

  0.950000     0.000051291978909      0.000018898570629  1.33289104579937e-17  4.43845264361253e-19
  0.951000     0.000050239954368      0.000018510931892  1.23765066121009e-16  -9.24959978401696e-19
  0.952000     0.000049189034371      0.000018123700958  1.67678642238461e-17  -5.38712954453735e-19
  0.953000     0.000048139216599      0.000017736876972  -2.86635949350855e-17  -2.37169225231204e-19
...
  0.994000     0.000006018054217      0.000002217256601  1.31645860662263e-17  1.15619997300212e-19
  0.995000     0.000005012529261      0.000001846785028  -4.15668713370839e-17  -3.5363625547867e-20
  0.996000     0.000004008013365      0.000001476685973  8.48811716443534e-17    8.470329472543e-22
  0.997000     0.000003004504507      0.000001106958687  1.44711343873661e-17  -2.92226366802734e-20
  0.998000     0.000002002000667      0.000000737602425   5.6734266807093e-18  -6.56450534122083e-21
  0.999000     0.000001000499833      0.000000368616443  -3.72557383333555e-17  1.47701370177469e-20

如果c变化不大,您可以为powc创建表,…顺便说一句,这不是卷积,只是一个简单的点积,所以我认为FFT没有任何用处here@geza:同意。这是一个乘法运算;FFT将把它变成一个微不足道的卷积。但是FFT和IFFT的价格似乎很高。只有在需要卷积时才应使用FFT,以使算法从^2变为logN。但是你的算法已经是最好的了,所以你不能通过切换算法来做得更好。你可以优化它,所以常数因子会更小。c的范围是什么?如果c变化不大,你可以为powc创建表格,…顺便说一句,这不是卷积,只是一个简单的点积,所以我认为FFT没有任何用处here@geza:同意。这是一个乘法运算;FFT将把它变成一个微不足道的卷积。但是FFT和IFFT的价格似乎很高。只有在需要卷积时才应使用FFT,以使算法从^2变为logN。但是你的算法已经是最好的了,所以你不能通过切换算法来做得更好。你可以优化它,所以常数因子会更小。c的范围是什么?更合理的选择是每8次或16次迭代一次;10不除以1024。更合理的选择是每第8次或第16次迭代;10不除以1024。最小运算计数:对于这些功率的评估,就误差累积而言,霍纳方案达到了最差的运算计数,ON。仅使用OLog N运算设计解决方案是可能的。对于具有一般系数序列的多项式求值,由于每个N系数都必须包含在过程中,因此不会比ON更好。因此,N次乘法和N次加法相当低。多点评估是另一个问题,其中傅里叶或基于插值的技术比每个采样点获得更好的复杂性。不,我不是说时间复杂性,我是说单点评估中的误差累积。[顺便说一下,方法
我指的是保持时间复杂性。]expm1似乎是避免精度损失的关键步骤。@YvesDaoust:你在考虑成对加法吗?我想这可能对我的回答更有好处。我首先为所有偶数I添加x[I]+c^1/N*x[I+1],将结果存储在x[I]中。然后加上x[i]+c^2/N*x[i+2],存储在x[i]中,以此类推。运算次数最少:对于这些功率的评估,就误差累积而言,霍纳方案的运算次数最差,为。仅使用OLog N运算设计解决方案是可能的。对于具有一般系数序列的多项式求值,由于每个N系数都必须包含在过程中,因此不会比ON更好。因此,N次乘法和N次加法相当低。多点评估是另一个问题,其中傅里叶或基于插值的技术比每个采样点获得更好的复杂性。不,我不是说时间复杂性,我是说单点评估中的误差累积。[顺便说一句,我所指的方法保持时间复杂性。]expm1似乎是避免精度损失的关键步骤。@YvesDaoust:你在考虑两两相加吗?我想这可能对我的回答更有好处。我首先为所有偶数I添加x[I]+c^1/N*x[I+1],将结果存储在x[I]中。然后添加x[i]+c^2/N*x[i+2],存储在x[i]中,等等。