Why does my sin calculating code in C return the wrong value?

牧云@^-^@ 提交于 2021-02-19 05:20:28

问题


I want to calculate the sinus of user inputs using my own functions based on this equation:

sin(x) = sum_(i=0)^n (-1)^i * (x^(2 i + 1)/((2 i + 1)!))

Picture of equation

I have this code and to my understandings I do exactly the same as what's written in the equation:

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

int faculty(int factor)
{
    int result = 1;

    if (factor > 0)
    {
        for (int i = 1; i <= factor; i++)
        {
            result = result * i;
        }
    }
    else
    {
        result = 1;
    }

    return result;
}

double my_sin(double x, int n)
{
    double my_sin = 0;
    for (int j = 0; j < n ; j++)
    {
        double i = (double)j;
        double faculty_j = (double)faculty(2*j+1);
        my_sin = my_sin + (pow((-1.0), i) * (pow(x, (double)(2.0 * i + 1.0)) / faculty_j));
    }

    return my_sin;
}

int main()
{
    int n = 0;
    double x = 0;

    printf("x=");
    scanf("%lf", &x);

    printf("n=");
    scanf("%i", &n);

    printf("sin(%i)=", (int)x);
    printf("%lf\n", my_sin(x, n));

    return 0;
}

However for example when I use x = 8 and n = 5 I get sin(8)=149 as a result. I tried debugging the code for some time now but I have no idea where the problem might be or how to find out what the problem is.


Updated code:

#include <stdio.h>

long int factorial(int factor)
{
    long int result = 1;

    if (factor > 0)
    {
        for (int i = 1; i <= factor; i++)
        {
            result = result * i;
        }
    }
    else
    {
        result = 1;
    }

    return result;
}

double my_pow(double a, double b)
{
    if (b == 0)
    {
        return 1;
    }

    double result = a;
    double increment = a;
    double i, j;

    for (i = 1; i < b; i++)
    {
        for (j = 1; j < a; j++)
        {
            result += increment;
        }
        increment = result;
    }
    return result;
}

double my_sin(double x, int n)
{
    double my_sin = 0;
    for (int j = 0; j < n ; j++)
    {
        double i = (double)j;
        double faculty_j = (double)factorial(2*i+1);
        my_sin = my_sin + (my_pow((-1.0), i) * (my_pow(x, 2.0 * i + 1.0) / faculty_j));
    }

    return my_sin;
}

int main()
{
    int n = 0;
    double x = 0;

    printf("x=");
    scanf_s("%lf", &x);

    printf("n=");
    scanf_s("%i", &n);

    printf("sin(%i)=", (int)x);
    printf("%lf\n", my_sin(x, n));

    return 0;
}

回答1:


The problem here is not necessarily your code. It's just your expectations on the equation. I tested the Taylor series for n=5 in Wolfram Alpha and subtracted sin(x) from the series, using the query

(sum (-1)^i * x^(2i+1)/(2i+1)!, i=0 to 5) - sin(x)  

to see the error.

https://www.wolframalpha.com/input/?i=%28sum+%28-1%29%5Ei+*+x%5E%282i%2B1%29%2F%282i%2B1%29%21%2C+i%3D0+to+5%29+-+sin%28x%29

As you can see, the series does a very good approximation when x is around 0, but when x goes above 3, the errors starts to be huge, and they are growing extremely fast. At x=8, the error has the size 67, which is obviously useless. To get a reasonable approximation, you need to use around n=15. Only five is way to little.

What you should do here is to take advantage of the fact that sin(x) = sin(x + k * 2 * PI) where k is an arbitrary integer. And if x is negative, you can just simple use the fact that sin(x) = -sin(-x). I'm showing a simple example of how it's done:

double my_sin(double x, int n)
{
    double my_sin = 0;
    double sign = 1.0;

    // sin(-x) = -sin(x)
    // This does not improve accuracy. It's just to make the rest simpler
    if(x<0) {
        x=-x;
        sign *= -1;
    }

    // sin(x) = sin(k*2*PI + x)
    x -= 2*PI*floor(x/(2*PI));

    // Continue as usual

    return sign * my_sin;
}

You want to be as close to x=0 as possible. In order to improve this even further, you can use the fact that sin(x) = -sin(x+PI), as shown below.

The above is enough to keep everything in the interval [0, PI], but we can actually do better than that by using some more clever math. We can use the fact that sin(x) is symmetric around x=PI/2 + k*PI. So sin(PI/2 -x) = sin(PI/2 + x). Utilizing that will keep you in the interval [0, PI/2] and when you're in that interval, n=2 is enough to get an error below 0.005. And for n=5, the error is below 10^-7. You can add this after the previous steps:

// sin(x) = -sin(x+PI)
if(x>PI) {
    x-=PI;
    sign *= -1;
}

// sin(PI/2 -x) = sin(PI/2 + x)
if(x>PI/2) 
    x = PI - x;

However, there is one bug in your code. Remember that the case j=n should be included, so change

for (int j = 0; j < n ; j++)

to

for (int j = 0; j <= n ; j++)

An alternative is to always call the function with 1 added to the argument.

And just for clarity. Remember that the Taylor series expects x to be in radians. If you're using degrees, you'll get the wrong result.

Just for completeness, here is the full function with a few other fixes that does not affect correctness but performance and readability:

double my_sin(double x, int n)
{
    double ret = 0;
    double sign = 1.0;

    if(x<0) {
        x=-x;
        sign *= -1;
    }

    x -= 2*PI*floor(x/(2*PI));

    if(x>PI) {
        x-=PI;
        sign *= -1;
    }

    if(x>PI/2) 
        x = PI - x;

    size_t denominator = 1;
    double numerator = x;
    int s = -1;

    for (int j = 0; j <= n ; j++) {
        denominator *= 2*j + 1;
        s *= -1;

        ret += s * numerator / denominator;

        numerator *= x*x;
    }

    return sign*ret;
}

Aiming for a certain precision

Say that you want 3 correct decimal digits. Then we can utilize the fact that the numerator grows slower than the denominator and do something like this:

    size_t denominator = 1;
    double numerator = x;
    double term;
    int s = -1;
    int j = 0;
    double tolerance = 0.001;

    do {
        denominator *= 2*j + 1;
        j++;
        s *= -1;

        term = numerator / denominator;

        ret += s * term;

        numerator *= x*x;
    } while(abs(term) > tolerance);



回答2:


There are many problems in your code:

  • the factorial function computes using integer arithmetic, which has a limited range. It will overflow with undefined behavior for n > 13 with 32-bit int or long.
  • the my_pow() function is incorrect: it does not work for negative values hence gives an inorrect result for my_pow(-1, ...).

You should also first reduce x to a number between 0 and PI/2 and compute the terms of the series incrementally.

Here is a modified version with a test suite:

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

long int factorial(int factor) {
    long int result = 1;

    if (factor > 0) {
        for (int i = 1; i <= factor; i++) {
            result = result * i;
        }
    } else {
        result = 1;
    }
    return result;
}

double my_pow(double a, double b) {
    if (b == 0) {
        return 1;
    }

    double result = a;
    double increment = a;
    double i, j;

    for (i = 1; i < b; i++) {
        for (j = 1; j < a; j++) {
            result += increment;
        }
        increment = result;
    }
    return result;
}

double my_sin(double x, int n) {
    double my_sin = 0;
    for (int j = 0; j < n; j++) {
        double i = (double)j;
        double faculty_j = (double)factorial(2 * i + 1);
        my_sin = my_sin + (my_pow((-1.0), i) * (my_pow(x, 2.0 * i + 1.0) / faculty_j));
    }
    return my_sin;
}

double my_sin2(double x, int n) {
    double my_sin, term, sign = 1.0;
    if (x < 0) {
        x = -x;
        sign = -sign;
    }
    if (x >= 2 * M_PI) {
        x = fmod(x, 2 * M_PI);
    }
    if (x > M_PI) {
        x -= M_PI;
        sign = -sign;
    }
    if (x > M_PI / 2) {
        x = M_PI - x;
    }
    my_sin = term = x * sign;
    for (int i = 1; i <= n; i++) {
        term = -term * x * x / ((2 * i) * (2 * i + 1));
        my_sin += term;
    }
    return my_sin;
}

int main() {
    int i, n = 16, steps = 100;
    double x1 = -7.0, x2 = 7.0;
#if 0
    printf("n=");
    scanf("%i", &n);
    printf("x1=");
    scanf("%lf", &x1);
    printf("x2=");
    scanf("%lf", &x2);
    printf("steps=");
    scanf("%i", &steps);
#endif
    char buf1[20];
    char buf2[20];
    snprintf(buf1, sizeof(buf1), "my_sin(x,%d)", n);
    snprintf(buf2, sizeof(buf2), "my_sin2(x,%d)", n);
    printf("%17s  %17s  %17s  %17s  %17s  %17s\n",
           "x", "sin(x)", buf1, "error1", buf2, "error2");
    for (i = 0; i <= steps; i++) {
        double x = x1 + (x2 - x1) * ((double)i / steps);
        double s = sin(x);
        double s1 = my_sin(x, n);
        double s2 = my_sin2(x, n);
        printf("%17.14f  %17.14f  %17.14f  %17.14f  %17.14f  %17.14f\n",
               x, s, s1, s1 - s, s2, s2 - s);
    }
    return 0;
}

Output:

                x             sin(x)       my_sin(x,16)             error1      my_sin2(x,16)             error2
-7.00000000000000  -0.65698659871879  -5.77359164449339  -5.11660504577460  -0.65698659871879  -0.00000000000000
-6.86000000000000  -0.54535677064030  -5.65811981160352  -5.11276304096322  -0.54535677064030   0.00000000000000
-6.72000000000000  -0.42305539714300  -5.54264797871365  -5.11959258157066  -0.42305539714300  -0.00000000000000
-6.58000000000000  -0.29247567242987  -5.42717614582379  -5.13470047339392  -0.29247567242987  -0.00000000000000
-6.44000000000000  -0.15617278154321  -5.31170431293392  -5.15553153139071  -0.15617278154321  -0.00000000000000
-6.30000000000000  -0.01681390048435  -5.19623248004405  -5.17941857955970  -0.01681390048435  -0.00000000000000
-6.16000000000000   0.12287399510655  -5.08076064715418  -5.20363464226073   0.12287399510655  -0.00000000000000
-6.02000000000000   0.26015749143047  -4.96528881426431  -5.22544630569478   0.26015749143047  -0.00000000000000
-5.88000000000000   0.39235022399145  -4.84981698137445  -5.24216720536590   0.39235022399145  -0.00000000000000
-5.74000000000000   0.51686544439743  -4.73434514848458  -5.25121059288201   0.51686544439743  -0.00000000000000
-5.60000000000000   0.63126663787232  -4.61887331559471  -5.25013995346703   0.63126663787232  -0.00000000000000
-5.46000000000000   0.73331520099566  -4.50340148270484  -5.23671668370050   0.73331520099566  -0.00000000000000
-5.32000000000000   0.82101424671125  -4.38792964981498  -5.20894389652622   0.82101424671125  -0.00000000000000
-5.18000000000000   0.89264767942823  -4.27245781692511  -5.16510549635334   0.89264767942823   0.00000000000000
-5.04000000000000   0.94681377559261  -4.15698598403524  -5.10379975962785   0.94681377559261  -0.00000000000000
-4.90000000000000   0.98245261262433  -4.04151415114537  -5.02396676376970   0.98245261262433  -0.00000000000000
-4.76000000000000   0.99886680949041  -3.92604231825550  -4.92490912774592   0.99886680949041   0.00000000000000
-4.62000000000000   0.99573517306225  -3.81057048536564  -4.80630565842788   0.99573517306225   0.00000000000000
-4.48000000000000   0.97311898322517  -3.69509865247577  -4.66821763570094   0.97311898322517   0.00000000000000
-4.34000000000000   0.93146079375324  -3.57962681958590  -4.51108761333914   0.93146079375324   0.00000000000000
-4.20000000000000   0.87157577241359  -3.46415498669603  -4.33573075910962   0.87157577241359   0.00000000000000
-4.06000000000000   0.79463574975740  -3.34868315380617  -4.14331890356356   0.79463574975740   0.00000000000000
-3.92000000000000   0.70214628873081  -3.23321132091630  -3.93535760964710   0.70214628873081   0.00000000000000
-3.78000000000000   0.59591722380776  -3.11773948802643  -3.71365671183419   0.59591722380776  -0.00000000000000
-3.64000000000000   0.47802724613534  -3.00226765513656  -3.48029490127191   0.47802724613534   0.00000000000000
-3.50000000000000   0.35078322768962  -2.88679582224669  -3.23757904993631   0.35078322768962   0.00000000000000
-3.36000000000000   0.21667508038738  -2.77132398935683  -2.98799906974421   0.21667508038738   0.00000000000000
-3.22000000000000   0.07832703347086  -2.65585215646696  -2.73417918993782   0.07832703347086   0.00000000000000
-3.08000000000000  -0.06155371742991  -2.54038032357709  -2.47882660614718  -0.06155371742991   0.00000000000000
-2.94000000000000  -0.20022998472177  -2.42490849068722  -2.22467850596545  -0.20022998472177   0.00000000000000
-2.80000000000000  -0.33498815015591  -2.30943665779736  -1.97444850764145  -0.33498815015590   0.00000000000000
-2.66000000000000  -0.46319126493035  -2.19396482490749  -1.73077355997714  -0.46319126493035   0.00000000000000
-2.52000000000000  -0.58233064952408  -2.07849299201762  -1.49616234249354  -0.58233064952408   0.00000000000000
-2.38000000000000  -0.69007498355694  -1.96302115912775  -1.27294617557082  -0.69007498355694   0.00000000000000
-2.24000000000000  -0.78431592508442  -1.84754932623788  -1.06323340115346  -0.78431592508442   0.00000000000000
-2.10000000000000  -0.86320936664887  -1.73207749334802  -0.86886812669914  -0.86320936664887   0.00000000000000
-1.96000000000000  -0.92521152078817  -1.61660566045815  -0.69139413966998  -0.92521152078817   0.00000000000000
-1.82000000000000  -0.96910912888046  -1.50113382756828  -0.53202469868783  -0.96910912888046   0.00000000000000
-1.68000000000000  -0.99404320219808  -1.38566199467841  -0.39161879248034  -0.99404320219808  -0.00000000000000
-1.54000000000000  -0.99952583060548  -1.27019016178855  -0.27066433118307  -0.99952583060548   0.00000000000000
-1.40000000000000  -0.98544972998846  -1.15471832889868  -0.16926859891022  -0.98544972998846   0.00000000000000
-1.26000000000000  -0.95209034159052  -1.03924649600881  -0.08715615441829  -0.95209034159052  -0.00000000000000
-1.12000000000000  -0.90010044217651  -0.92377466311894  -0.02367422094244  -0.90010044217651   0.00000000000000
-0.98000000000000  -0.83049737049197  -0.80830283022907   0.02219454026290  -0.83049737049197  -0.00000000000000
-0.84000000000000  -0.74464311997086  -0.69283099733921   0.05181212263165  -0.74464311997086   0.00000000000000
-0.70000000000000  -0.64421768723769  -0.57735916444934   0.06685852278835  -0.64421768723769   0.00000000000000
-0.56000000000000  -0.53118619792088  -0.46188733155947   0.06929886636141  -0.53118619792088   0.00000000000000
-0.42000000000000  -0.40776045305957  -0.34641549866960   0.06134495438997  -0.40776045305957  -0.00000000000000
-0.28000000000000  -0.27635564856411  -0.23094366577974   0.04541198278438  -0.27635564856411   0.00000000000000
-0.14000000000000  -0.13954311464424  -0.11547183288987   0.02407128175437  -0.13954311464424   0.00000000000000
 0.00000000000000   0.00000000000000   0.00000000000000   0.00000000000000   0.00000000000000   0.00000000000000
 0.14000000000000   0.13954311464424   0.11547183288987  -0.02407128175437   0.13954311464424   0.00000000000000
 0.28000000000000   0.27635564856411   0.23094366577974  -0.04541198278438   0.27635564856411   0.00000000000000
 0.42000000000000   0.40776045305957   0.34641549866960  -0.06134495438997   0.40776045305957   0.00000000000000
 0.56000000000000   0.53118619792088   0.46188733155947  -0.06929886636141   0.53118619792088   0.00000000000000
 0.70000000000000   0.64421768723769   0.57735916444934  -0.06685852278835   0.64421768723769   0.00000000000000
 0.84000000000000   0.74464311997086   0.69283099733921  -0.05181212263165   0.74464311997086   0.00000000000000
 0.98000000000000   0.83049737049197   0.80830283022907  -0.02219454026290   0.83049737049197   0.00000000000000
 1.12000000000000   0.90010044217650   0.20895817141848  -0.69114227075803   0.90010044217650   0.00000000000000
 1.26000000000000   0.95209034159052   0.23507794284579  -0.71701239874473   0.95209034159052   0.00000000000000
 1.40000000000000   0.98544972998846   0.26119771427310  -0.72425201571536   0.98544972998846   0.00000000000000
 1.54000000000000   0.99952583060548   0.28731748570041  -0.71220834490507   0.99952583060548   0.00000000000000
 1.68000000000000   0.99404320219808   0.31343725712772  -0.68060594507036   0.99404320219808   0.00000000000000
 1.82000000000000   0.96910912888046   0.33955702855503  -0.62955210032543   0.96910912888046  -0.00000000000000
 1.96000000000000   0.92521152078817   0.36567679998234  -0.55953472080583   0.92521152078817   0.00000000000000
 2.10000000000000   0.86320936664887  -2.81259124559189  -3.67580061224076   0.86320936664887   0.00000000000000
 2.24000000000000   0.78431592508442  -3.00009732863135  -3.78441325371577   0.78431592508442  -0.00000000000000
 2.38000000000000   0.69007498355694  -3.18760341167081  -3.87767839522775   0.69007498355694  -0.00000000000000
 2.52000000000000   0.58233064952408  -3.37510949471027  -3.95744014423435   0.58233064952408  -0.00000000000000
 2.66000000000000   0.46319126493035  -3.56261557774973  -4.02580684268007   0.46319126493035   0.00000000000000
 2.80000000000000   0.33498815015591  -3.75012166078919  -4.08510981094509   0.33498815015591  -0.00000000000000
 2.94000000000000   0.20022998472177  -3.93762774382865  -4.13785772855042   0.20022998472177  -0.00000000000000
 3.08000000000000   0.06155371742991  -15.52969329095525  -15.59124700838516   0.06155371742991  -0.00000000000000
 3.22000000000000  -0.07832703347086  -16.23558844054412  -16.15726140707325  -0.07832703347086  -0.00000000000000
 3.36000000000000  -0.21667508038738  -16.94148359013299  -16.72480850974561  -0.21667508038738  -0.00000000000000
 3.50000000000000  -0.35078322768962  -17.64737873972186  -17.29659551203224  -0.35078322768962  -0.00000000000000
 3.64000000000000  -0.47802724613534  -18.35327388931075  -17.87524664317541  -0.47802724613534  -0.00000000000000
 3.78000000000000  -0.59591722380777  -19.05916903889963  -18.46325181509186  -0.59591722380776   0.00000000000000
 3.92000000000000  -0.70214628873081  -19.76506418848850  -19.06291789975770  -0.70214628873081  -0.00000000000000
 4.06000000000000  -0.79463574975740  -785.98288527038937  -785.18824952063198  -0.79463574975740  -0.00000000000000
 4.20000000000000  -0.87157577241359  -813.08574338316134  -812.21416761074772  -0.87157577241359   0.00000000000000
 4.34000000000000  -0.93146079375324  -840.18860149593320  -839.25714070217998  -0.93146079375324   0.00000000000000
 4.48000000000000  -0.97311898322517  -867.29145960870540  -866.31834062548023  -0.97311898322517   0.00000000000000
 4.62000000000000  -0.99573517306225  -894.39431772147736  -893.39858254841511  -0.99573517306225  -0.00000000000000
 4.76000000000000  -0.99886680949041  -921.49717583424911  -920.49830902475867  -0.99886680949041  -0.00000000000000
 4.90000000000000  -0.98245261262433  -948.60003394702130  -947.61758133439696  -0.98245261262433   0.00000000000000
 5.04000000000000  -0.94681377559261  -218497.22569403689704  -218496.27888026129222  -0.94681377559261   0.00000000000000
 5.18000000000000  -0.89264767942823  -224566.59307442686986  -224565.70042674744036  -0.89264767942823   0.00000000000000
 5.32000000000000  -0.82101424671125  -230635.96045481678448  -230635.13944057008484  -0.82101424671125   0.00000000000000
 5.46000000000000  -0.73331520099566  -236705.32783520672820  -236704.59452000574674  -0.73331520099566   0.00000000000000
 5.60000000000000  -0.63126663787232  -242774.69521559661371  -242774.06394895873382  -0.63126663787232   0.00000000000000
 5.74000000000000  -0.51686544439743  -248844.06259598652832  -248843.54573054212960  -0.51686544439743   0.00000000000000
 5.88000000000000  -0.39235022399145  -254913.42997637641383  -254913.03762615242158  -0.39235022399145   0.00000000000000
 6.02000000000000  -0.26015749143047  -26739506.86298683285713  -26739506.60282934084535  -0.26015749143047   0.00000000000000
 6.16000000000000  -0.12287399510655  -27361355.85980049148202  -27361355.73692649602890  -0.12287399510655   0.00000000000000
 6.30000000000000   0.01681390048435  -27983204.85661410912871  -27983204.87342800945044   0.01681390048435   0.00000000000000
 6.44000000000000   0.15617278154321  -28605053.85342779010534  -28605054.00960057228804   0.15617278154321   0.00000000000000
 6.58000000000000   0.29247567242987  -29226902.85024143010378  -29226903.14271710067987   0.29247567242987   0.00000000000000
 6.72000000000000   0.42305539714300  -29848751.84705507382751  -29848752.27011046931148   0.42305539714300   0.00000000000000
 6.86000000000000   0.54535677064030  -30470600.84386872500181  -30470601.38922549411654   0.54535677064030   0.00000000000000
 7.00000000000000   0.65698659871879  -31092449.84068236500025  -31092450.49766896292567   0.65698659871879   0.00000000000000


来源:https://stackoverflow.com/questions/65870518/why-does-my-sin-calculating-code-in-c-return-the-wrong-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!