简体   繁体   English

C++中的复合辛普森规则

[英]Composite Simpson's Rule in C++

I've been trying to write a function to approximate an the value of an integral using the Composite Simpson's Rule.我一直在尝试编写一个函数来使用复合辛普森规则来近似积分的值。

template <typename func_type>
double simp_rule(double a, double b, int n, func_type f){

    int i = 1; double area = 0;
    double n2 = n;
    double h = (b-a)/(n2-1), x=a;

    while(i <= n){

        area = area + f(x)*pow(2,i%2 + 1)*h/3;
        x+=h;
        i++;
    }
    area -= (f(a) * h/3);
    area -= (f(b) * h/3);

    return area;
    }

What I do is multiply each value of the function by either 2 or 4 (and h/3) with pow(2,i%2 + 1) and subtract off the edges as these should only have a weight of 1.我所做的是将函数的每个值乘以 2 或 4(和 h/3)与pow(2,i%2 + 1)并减去边缘,因为它们的权重仅为 1。

At first, I thought it worked just fine, however, when I compared it to my Trapezoidal Method function it was way more inaccurate which shouldn't be the case.起初,我认为它工作得很好,但是,当我将它与我的梯形方法函数进行比较时,它更不准确,这不应该是这种情况。

This is a simpler version of a code I previously wrote which had the same problem, I thought that if I cleaned it up a little the problem would go away, but alas.这是我之前编写的代码的更简单版本,它有同样的问题,我认为如果我稍微清理一下,问题就会消失,但唉。 From another post, I get the idea that there's something going on with the types and the operations I'm doing on them which results in loss of precision, but I just don't see it.从另一篇文章中,我了解到类型和我对它们执行的操作会导致精度损失,但我只是没有看到。

Edit:编辑:

For completeness, I was running it for e^x from 1 to zero为了完整起见,我正在为 e^x 从 1 到 0 运行它

\\function to be approximated
double f(double x){ double a = exp(x); return a; }

int main() {

    int n = 11; //this method works best for odd values of n
    double e = exp(1);
    double exact = e-1; //value of integral of e^x from 0 to 1

    cout << simp_rule(0,1,n,f) - exact;

The Simpson's Rule uses this approximation to estimate a definite integral:辛普森法则使用这个近似值来估计一个定积分:

Where在哪里

and

So that there are n + 1 equally spaced sample points x i .这样就有n + 1 个等距的样本点x i

In the posted code, the parameter n passed to the function appears to be the number of points where the function is sampled (while in the previous formula n is the number of intervals, that's not a problem).在发布的代码中,传递给函数的参数n似乎是对函数进行采样的点数(而在前面的公式中n是间隔数,这不是问题)。

The (constant) distance between the points is calculated correctly正确计算点之间的(恒定)距离

double h = (b - a) / (n - 1);

The while loop used to sum the weighted contributes of all the points iterates from x = a up to a point with an ascissa close to b , but probably not exactly b , due to rounding errors.用于对所有点的加权贡献求和的 while 循环从x = a迭代到 ascissa 接近b ,但由于舍入误差,可能不完全是b This implies that the last calculated value of f , f(x_n) , may be slightly different from the expected f(b) .这意味着f的最后计算值f(x_n)可能与预期的f(b)略有不同。

This is nothing, though, compared to the error caused by the fact that those end points are summed inside the loop with the starting weight of 4 and then subtracted after the loop with weight 1 , while all the inner points have their weight switched.但是,与以下事实所导致的错误相比,这算不了什么:这些端点在循环内以4的起始权重相加,然后在循环后以权重1减去,而所有内部点的权重都切换了。 As a matter of fact, this is what the code calculates:事实上,这是代码计算的:

\\frac{\\Delta x}{3}\\left ( 3f(x_0)+ 2f(x_1) + 4f(x_2) + ... + 2f(x_{n-1}) + 3f(x_{n}) \\对 )

Also, using另外,使用

pow(2, i%2 + 1) 

To generate the sequence 4, 2, 4, 2, ..., 4 is a waste, in terms of efficency, and may add (depending on the implementation) other unnecessary rounding errors.生成序列 4, 2, 4, 2, ..., 4 就效率而言是一种浪费,并且可能会添加(取决于实现)其他不必要的舍入误差。

The following algorithm shows how to obtain the same (fixed) result, without a call to that library function.以下算法显示了如何在不调用该库函数的情况下获得相同(固定)的结果。

template <typename func_type>
double simpson_rule(double a, double b,
                    int n, // Number of intervals
                    func_type f)
{
    double h = (b - a) / n;

    // Internal sample points, there should be n - 1 of them
    double sum_odds = 0.0;
    for (int i = 1; i < n; i += 2)
    {
        sum_odds += f(a + i * h);
    }
    double sum_evens = 0.0;
    for (int i = 2; i < n; i += 2)
    {
        sum_evens += f(a + i * h);
    }

    return (f(a) + f(b) + 2 * sum_evens + 4 * sum_odds) * h / 3;
}

Note that this function requires the number of intervals (eg use 10 instead of 11 to obtain the same results of OP's function) to be passed, not the number of points.请注意,此函数需要传递的间隔数(例如,使用 10 而不是 11 来获得与 OP 函数相同的结果),而不是点数。

Testable here .在这里测试。

The above excellent and accepted solution could benefit from liberal use of std::fma() and templatize on the floating point type.上述优秀且公认的解决方案可以受益于std::fma()自由使用和浮点类型的模板化。 https://en.cppreference.com/w/cpp/numeric/math/fma https://en.cppreference.com/w/cpp/numeric/math/fma

#include <cmath>
template <typename fptype, typename func_type>
double simpson_rule(fptype a, fptype b,
                    int n, // Number of intervals
                    func_type f)
{
    fptype h = (b - a) / n;

    // Internal sample points, there should be n - 1 of them
    fptype sum_odds = 0.0;
    for (int i = 1; i < n; i += 2)
    {
        sum_odds += f(std::fma(i,h,a));
    }
    fptype sum_evens = 0.0;
    for (int i = 2; i < n; i += 2)
    {
        sum_evens += f(std::fma(i,h,a);
    }

    return (std::fma(2,sum_evens,f(a)) + 
            std::fma(4,sum_odds,f(b))) * h / 3;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM