【问题标题】:Why does C give different values to Python for this algorithm?为什么 C 为这个算法赋予 Python 不同的值?
【发布时间】:2017-01-20 10:14:52
【问题描述】:

我用 C 语言编写了一个简短的程序来执行线性插值,它通过迭代将函数的根赋予给定的小数点数。到目前为止,对于一个函数 f(x):

    long double f(long double x) {
        return (pow(x, 3) + 2 * x - 8);
    }

程序未能收敛到 1dp 值。程序更新变量 a 和 b,f(x) 的根位于变量 a 和 b 之间,直到 a 和 b 都以给定的精度四舍五入到相同的数字。使用 long doubles 和上述函数,调试器会显示前 2 次迭代:

    a = 1.5555555555555556
    a = 1.6444444444444444

虽然它应该是:

    a = 1.5555555555555556
    a = 1.653104925053533

在此之后程序无法更新值。我使用的线性插值方程是给定here 的数学方程的重新排列版本,我使用的代码是我编写的 python 程序的 C 版本。尽管算法相同,为什么 C 实现会得到不同的值,我该如何解决?

好的,我仍然掌握了这个窍门,但希望下面我有一个最小、完整和可验证的示例:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

long double a; long double b; long double c; // The values for interpolation
long double fofa; long double fofb; long double fofc; // The values f(a), f(b) and f(c)
const int dp = 1; // The number of decimal places to be accurate to

long double f(long double x) {
    return (pow(x, 3) + 2 * x - 8);
}

int main(void) {
    a = 1; b = 2;
    while(roundf(a * pow(10, dp))/pow(10, dp) != roundf(b * pow(10, dp))/pow(10, dp)) { // While a and b don't round to the same number...

        fofa = f(a); fofb = f(b); // Resolve the functions
        printf("So f(a) = %g, f(b) = %g\n", (double)fofa, (double)fofb); // Print the results

        c = (b * abs(fofa) + a * abs(fofb)) / (abs(fofb) + abs(fofa)); // Linear Interpolation

        fofc = f(c);

        if(fofc < 0) {
            a = c;
        }
        else if(fofc == 0) {
            a = c;
            break;
        }
        else {
            b = c;
        }

    }

    printf("The root is %g, to %d decimal places, after %d iterations.\n", (double)a, dp, i);
}

【问题讨论】:

  • 你不能使用一些调试来查看你得到的fofa(我假设是f(a))、fofb 和所有其他中间值的值吗?
  • 您使用什么作为dp 的值(1、10、15,其他)?当long double 用于afofa 等时,为什么还要使用float-precision 和roundf()? (你应该使用roundl()powl()(而不是pow()),除非你使用&lt;tgmath.h&gt;,但是你应该提到如果你是并且你不会使用roundf() 但是只是round()
  • 请提供minimal reproducible example,而不是益智游戏。另见How to Ask
  • 十进制数字与二进制计算的准确性有什么关系?您应该使用 epsilon 吗?我很想链接维基百科页面,但this SO question 更值得深思。

标签: c math linear-interpolation


【解决方案1】:

函数abs()(来自&lt;stdlib.h&gt;)的签名是int abs(int);——您从计算中得到整数。

您应该使用来自&lt;math.h&gt;long double fabsl(long double);

您还应该使用powl() 而不是pow()long doubledouble),以及roundl() 而不是roundf()long doublefloat)。

换句话说,请确保您使用的是正确的类型。


当您解决了类型问题后,您仍然会遇到收敛问题。如果您制作了 MCVE (Minimal, Complete, Verifiable Example),那将会有所帮助。但是,我可以从您的问题中推断出这是一个 MCVE:

#include <math.h>
#include <stdio.h>

static inline long double f(long double x)
{
    return(powl(x, 3) + 2 * x - 8);
}

int main(void)
{
    long double a = 1.0L;
    long double b = 2.0L;
    int dp = 6;

    while (roundl(a * powl(10, dp)) / powl(10, dp) != roundl(b * powl(10, dp)) / powl(10, dp))
    {
        long double fofa = f(a);
        long double fofb = f(b);
        long double c = (b * fabsl(fofa) + a * fabsl(fofb)) / (fabsl(fofb) + fabsl(fofa));
        long double fofc = f(c);
        printf("a = %+.10Lf, f(a) = %+.10Lf\n", a, fofa);
        printf("b = %+.10Lf, f(b) = %+.10Lf\n", b, fofb);
        printf("c = %+.10Lf, f(c) = %+.10Lf\n", c, fofc);
        putchar('\n');

        if (fofc < 0.0L)
        {
            a = c;
        }
        else if (fofc == 0.0L)
        {
            a = c;
            break;
        }
        else
        {
            b = c;
        }
    }
    printf("Result: a = %Lg\n", a);
    return 0;
}

我从中得到的输出是:

a = +1.0000000000, f(a) = -5.0000000000
b = +2.0000000000, f(b) = +4.0000000000
c = +1.5555555556, f(c) = -1.1248285322

a = +1.5555555556, f(a) = -1.1248285322
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6531049251, f(c) = -0.1762579238

a = +1.6531049251, f(a) = -0.1762579238
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6677455452, f(c) = -0.0258828049

a = +1.6677455452, f(a) = -0.0258828049
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6698816424, f(c) = -0.0037639074

a = +1.6698816424, f(a) = -0.0037639074
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6701919841, f(c) = -0.0005465735

a = +1.6701919841, f(a) = -0.0005465735
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702370440, f(c) = -0.0000793539

a = +1.6702370440, f(a) = -0.0000793539
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702435859, f(c) = -0.0000115206

a = +1.6702435859, f(a) = -0.0000115206
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702445357, f(c) = -0.0000016726

a = +1.6702445357, f(a) = -0.0000016726
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446735, f(c) = -0.0000002428

a = +1.6702446735, f(a) = -0.0000002428
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446936, f(c) = -0.0000000353

a = +1.6702446936, f(a) = -0.0000000353
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446965, f(c) = -0.0000000051

a = +1.6702446965, f(a) = -0.0000000051
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446969, f(c) = -0.0000000007

a = +1.6702446969, f(a) = -0.0000000007
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446970, f(c) = -0.0000000001

a = +1.6702446970, f(a) = -0.0000000001
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446970, f(c) = -0.0000000000

a = +1.6702446970, f(a) = -0.0000000000
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446970, f(c) = -0.0000000000

a = +1.6702446970, f(a) = -0.0000000000
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446970, f(c) = -0.0000000000

a = +1.6702446970, f(a) = -0.0000000000
b = +2.0000000000, f(b) = +4.0000000000
c = +1.6702446970, f(c) = -0.0000000000

死循环的原因很清楚; ab 之间的区别不小。您需要检查您的收敛标准。它可能应该将fofc0.0 比较到给定的小数位数内——或者类似的东西。

【讨论】:

  • 非常感谢,令人惊讶的是它现在确实收敛了(对于太多的 dp,但我会弄清楚这一点!)。
  • 按照Lutzlanswer 的提示,您可能会发现False Position Method 上的维基百科文章很有启发性。我没有尝试进一步修正数学。
【解决方案2】:

您正在实施的称为 regula falsifalse position method

只要保持相反的符号条件,实际上不需要使用绝对值。

plain vanilla regula falsi 有一个众所周知的停滞问题,即在函数在剩余区间上凸出的那一刻,一个端点将不再向根移动。有一些简单的修改可以避免这种情况,例如插入二等分步骤。 Illinois 修改 更容易实现但更难理解。有关详细信息,请参阅 Wikipedia 文章以了解 regula falsi。

或者这个问答:Regula-Falsi Algorithm?


改编自链接中的答案:

#include<math.h>
#include<stdio.h>

long double f(long double x) {
   return powl(x, 3) + 2 * x - 8;
}

int main(void) {
    const int dp = 18;
    long double eps=0.5*powl(10,-dp);
    int i=0;
    long double a=1, fofa = f(a);
    long double b=2, fofb = f(b);

    printf("\na=%.21Lf b=%.21Lf  fofa=%.21Lf  fofb=%.21Lf\n------\n",a,b, fofa,fofb);

    if(signbit(fofb)==signbit(fofa)) {
        printf("Warning, initial values do not have opposite sign!\n");
    }
    do {
        long double c=(a*fofb-b*fofa)/(fofb-fofa), fofc = f(c);

        if( signbit(fofc)!=signbit(fofa) ) {
            b=a; fofb=fofa;
            a=c; fofa=fofc;
        } else {
            a=c; fofa=fofc;
            fofb *= 0.5;
        }
        i++;  
        printf("\na=c=%.21Lf b=%.21Lf  fofa=fofc=%.21Lf  fofb=%.21Lf",c,b, fofc,fofb);

    } while(fabsl(b-a)>eps);

    printf("\ngoal reached after %d iterations\n",i);

    return 0;
}

结果

a=1.000000000000000000000 b=2.000000000000000000000  fofa=-5.000000000000000000000  fofb=4.000000000000000000000
------

a=c=1.555555555555555555507 b=2.000000000000000000000  fofa=fofc=-1.124828532235939643549  fofb=2.000000000000000000000
a=c=1.715539947322212467064 b=1.555555555555555555507  fofa=fofc=0.480046589479470829469  fofb=-1.124828532235939643549
a=c=1.667685780603345490963 b=1.715539947322212467064  fofa=fofc=-0.026500999000164700194  fofb=0.480046589479470829469
a=c=1.670189362207942139265 b=1.715539947322212467064  fofa=fofc=-0.000573759143326624515  fofb=0.240023294739735414734
a=c=1.670297511133477909220 b=1.670189362207942139265  fofa=fofc=0.000547652143260468627  fofb=-0.000573759143326624515
a=c=1.670244695550498326532 b=1.670297511133477909220  fofa=fofc=-0.000000014643676336194  fofb=0.000547652143260468627
a=c=1.670244696962696986627 b=1.670297511133477909220  fofa=fofc=-0.000000000000373724489  fofb=0.000273826071630234313
a=c=1.670244696962769068724 b=1.670244696962696986627  fofa=fofc=0.000000000000373706274  fofb=-0.000000000000373724489
a=c=1.670244696962733028543 b=1.670244696962769068724  fofa=fofc=-0.000000000000000000434  fofb=0.000000000000373706274
a=c=1.670244696962733028651 b=1.670244696962733028543  fofa=fofc=0.000000000000000000867  fofb=-0.000000000000000000434
goal reached after 10 iterations

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-18
    • 2020-10-31
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    相关资源
    最近更新 更多