【问题标题】:Approximation of arcsin in CC中arcsin的近似值
【发布时间】:2013-12-08 07:21:12
【问题描述】:

我有一个程序可以根据泰勒级数计算反正弦值的近似值。

我和我的朋友想出了一个算法,它能够返回几乎“正确”的值,但我认为我们做得不是很清楚。看看:

double my_asin(double x)
{
    double a = 0;
    int i = 0;
    double sum = 0;
    a = x;
    for(i = 1; i < 23500; i++)
    {
        sum += a;
        a = next(a, x, i);
    }
}

double next(double a, double x, int i)
{
    return a*((my_pow(2*i-1, 2)) / ((2*i)*(2*i+1)*my_pow(x, 2)));
}

我检查了 my_pow 是否正常工作,所以我也不需要在这里发布它。基本上,一旦当前项和下一项之间的差异大于或等于我的 EPSILON (0.00001),我希望循环结束,这是我在计算平方根时使用的精度。


这就是我希望它的工作方式:

while(my_abs(prev_term - next_term) >= EPSILON)

但是函数 double next 依赖于 i,所以我想我也必须在 while 语句中增加它。有什么想法我应该怎么做?


-1 的示例输出:

$ -1.5675516116e+00

代替:

$ -1.5707963268e+00

非常感谢大家。

【问题讨论】:

  • 我想我需要的是前项和下一项的绝对值之间的差异,而不是仅仅将 epsilon 与一个数字进行比较。我要试试看。
  • 请注意,你的第一个公式是错误的,所有的泰勒级数的 arcsin 都有一个+ 符号。
  • @MartinR 我不认为这是错的。
  • @Foxy,这显然是错误的。您如何从中获得第三学期?
  • 所有术语都有一个+号。在您的next() 函数中似乎是正确的,只有公式的第一个屏幕截图是错误的。

标签: c algorithm precision trigonometry taylor-series


【解决方案1】:

您的代码和问题的问题包括:

  1. 显示 arcsin 泰勒级数的图像文件有两个错误:x5 项上有一个减号,而不是一个加号,以及x 显示为 xn 但应为 x2n+1.
  2. 泰勒级数中的 x 因子在每一项中增加 x2 ,但您的公式 a*((my_pow(2*i-1, 2)) / ((2*i)*(2*i+1)*my_pow(x, 2))) 在每个术语中 除以 x2。这对于您询问的特定值 -1 无关紧要,但对于除 1 之外的其他值,它会产生错误的结果。
  3. 您询问如何在术语差异“大于或等于”您的 epsilon 时结束循环,但是对于 x 的大多数值,您实际上希望小于(或者相反,您希望继续,而不是结束,而差异大于或等于,如您在代码中所示)。
  4. Taylor 级数是评估函数的一种糟糕方法,因为它的误差会随着您远离级数的中心点而增加。大多数此类函数的数学库实现都使用极小极大数列或与之相关的东西。
  5. 从低阶项到高阶项评估序列会导致您先添加较大的值,然后再添加较小的值。由于浮点运算的性质,这意味着较小项的准确性会丢失,因为它被较大的值“推出”了浮点格式的宽度。这种影响会限制任何结果的准确性。
  6. 最后,为了直接解决您的问题,您构建代码的方式,您直接更新a,因此您永远不会同时拥有上一期和下一期。相反,请创建另一个 double b,以便为上一个术语创建一个对象 b,为当前术语创建一个对象 a,如下所示。

例子:

double a = x, b, sum = a;
int i = 0;
do
{
    b = a;
    a = next(a, x, ++i);
    sum += a;
} while (abs(b-a) > threshold);

【讨论】:

  • 您好,非常感谢您的意见。到目前为止,您的示例对我帮助很大,并产生了更好的结果。但是,我不太确定我是否理解您在第 2 点中的意思。我应该更改公式以增加 x^2 吗?目前,asin(1) 的输出是1.2416666667e+00,阈值为 0.00000000001...
  • @Foxxy:第 2 点只是指出图像不正确。该代码在同一方面并不正确。值 1.2416666667e+00 是评估 3 个项(对于 i = 0、1 和 2)所得到的值,所以我希望这意味着您的循环比预期的更早终止。
  • 广告#6:除非我弄错了,否则该系列的术语不会交替出现。对于正 x,所有项都是正的,对于负 x,所有项都是负的。
  • 我编辑了这个问题,以考虑到该系列没有交替的事实。 (我可能被图像中不正确的 x**n 吓倒了。)请注意,生成的代码适用于反正弦,但如果用于交替系列,则需要进行调整。
  • @EricPostpischil asin(-1) 现在在 17808 次迭代中给我$ -1.5665686059e+00。当我尝试将 EPSILON 增加到超过#define EPSILON2 0.00000000001 时,结果是-nan。
【解决方案2】:

arcsin 使用 Taylor 级数 是极其不精确的,因为这些东西的收敛性非常差,并且对于有限数量的 therms,与真实的东西会有相对较大的差异。同样使用带有整数指数的pow 也不是很精确和有效。

不过使用arctan 就可以了

arcsin(x) = arctan(x/sqrt(1-(x*x)));

当它的泰勒级数在&lt;0.0,0.8&gt; 范围内收敛时,范围的所有其他部分都可以通过它计算(使用三角恒等式)。所以这里是我的 C++ 实现(来自我的算术模板):

T atan    (const T &x)                                              // = atan(x)
    {
    bool _shift=false;
    bool _invert=false;
    bool _negative=false;
    T z,dz,x1,x2,a,b; int i;
    x1=x; if (x1<0.0) { _negative=true; x1=-x1; }
    if (x1>1.0) { _invert=true; x1=1.0/x1; }
    if (x1>0.7) { _shift=true; b=::sqrt(3.0)/3.0; x1=(x1-b)/(1.0+(x1*b)); }
    x2=x1*x1;
    for (z=x1,a=x1,b=1,i=1;i<1000;i++)  // if x1>0.8 convergence is slow
        {
        a*=x2; b+=2; dz=a/b; z-=dz;
        a*=x2; b+=2; dz=a/b; z+=dz;
        if (::abs(dz)<zero) break;
        }
    if (_shift) z+=pi/6.0;
    if (_invert) z=0.5*pi-z;
    if (_negative) z=-z;
    return z;
    }
T asin    (const T &x)                                              // = asin(x)
    {
    if (x<=-1.0) return -0.5*pi;
    if (x>=+1.0) return +0.5*pi;
    return ::atan(x/::sqrt(1.0-(x*x)));
    }

其中T 是任何浮点类型(float,double,...)。如您所见,您需要实现sqrt(x)pi=3.141592653589793238462643383279502884197169399375105zero=1e-20+,-,*,/ 操作。 zero 常量是目标精度。

所以只需将T 替换为float/double 并忽略:: ...

【讨论】:

    【解决方案3】:

    所以我想我也必须在 while 语句中增加它

    是的,这可能是一种方法。是什么阻止了你?

    int i=0;
    while(condition){
       //do something
       i++;
    }
    

    另一种方法是使用 for 条件:

    for(i = 1; i < 23500 && my_abs(prev_term - next_term) >= EPSILON; i++)
    

    【讨论】:

    • 我删除了 i 的条件,因为它实际上只是为了在某个时候结束循环。 for(i = 1; my_abs(sum - a) &gt;= EPSILON; i++) 只是给了我奇怪的值,所以我猜它在第一学期就失败了。
    • Wmy my_abs(sum - a)?我认为这种情况是错误的(项目变得越来越小,因此这种差异正在增加)。再次检查你的想法。此外,您可能希望使用问题 cmets 中建议的条件@Nico Schertler。
    • double prev_term = 1; double next_term = 0.5*(x / prev_term + prev_term); while(my_abs(prev_term - next_term) &gt;= EPSILON) { prev_term = next_term; next_term = 0.5*(x / prev_term + prev_term); } return next_term; 这在平方根计算中非常适合我。
    【解决方案4】:

    你的公式是错误的。这是正确的公式:http://scipp.ucsc.edu/~haber/ph116A/taylor11.pdf

    附:另请注意,您的公式和您的系列不对应。


    你可以像这样使用while:

    while( std::abs(sum_prev - sum) < 1e-15 )
        {
            sum_prev = sum;
            sum += a;
            a = next(a, x, i);
        }
    

    【讨论】:

    • @Foxy,这能解决你的问题吗?至少有帮助吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-19
    • 2014-10-26
    • 2023-03-05
    • 1970-01-01
    • 2018-10-17
    • 1970-01-01
    相关资源
    最近更新 更多