问题
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)!))
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 forn > 13
with 32-bitint
orlong
. - the
my_pow()
function is incorrect: it does not work for negative values hence gives an inorrect result formy_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