So, I'm trying to create a program that calculates cos(x) by using a Taylor approximation.
The program is really simple: The user inputs a parameter x (x being an angle in radians) and a float ε, which is the precision of the value of cos(x).
Basically, the only thing the program has to do is to calculate this sum:
x^0/0! - x^2/2! + x^4/4! - x^6! + x^8/8! - ..., until the terms are smaller than ε, that is, the value for cos(x) it'll be within our range of precision.
So, here's the code:
#include <stdio.h>
/* Calculates cos(x) by using a Taylor approximation:
cos(x) = x^0/(0!) - x^2/(2!) + x^4/(4!) - x^6/(6!) + x^8/(8!) - ... */
int main(void)
{
int k; // dummy variable k
float x, // parameter of cos(x), in radians
epsilon; // precision of cos(x) (cos = sum ± epsilon)
sum, // sum of the terms of the polynomial series
term; // variable that stores each term of the summation
scanf("%f %f", &x, &epsilon);
sum = term = 1, k = 0;
while (term >= epsilon && -term <= epsilon)
// while abs(term) is smaller than epsilon
{
k += 2;
term *= -(x*x)/(k*(k-1));
sum += term;
}
printf("cos(%f) = %f\n", x, sum);
return 0;
}
At first, I tried to solve it by calculating the factorials on a separate variable "fact", though that caused an overflow even with reasonable large values for ε.
To solve this, I noticed that I could just multiply the previous term by -x² / (k(k - 1)), increasing k by 2 in every iteration, to get the next term. I thought that would solve my problem, but then again, it is not working.
The program compiles fine, but for example, if I input:
3.141593 0.001
The output is:
cos(3.141593) = -3.934803
...and that is obviously wrong. Can someone help me?
The bug lies in the condition of your while loop:
while (term >= epsilon && -term <= epsilon)
It's not the correct condition. While it could be fixed by fixing the logic:
while (term >= epsilon || -term >= epsilon)
You should just use the standard floating point abs function, fabs, as it makes the function of your code more obvious:
while (fabs(term) >= epsilon)
After applying that change and compiling your program I used it to compute cos(3.141593) = -1.000004, which is correct.
Just adding to Charliehorse55's answer.
Usually one does an argument reduction using simple trigonometry
cos(x + y) = cos(x)cos(y) - sin(x)sin(y)
sin(x + y) = cos(x)sin(y) + sin(x)cos(y)
to reduce the argument to [0..SmallAngle] range and only then calculate the Taylor expansion.
Related
I was calculating e^x using Taylor Series and noticed that when we calculate it for negative x absolute error is large.Is it because we don't have enough precision to calculate it?
(I know that to prevent it we can use e^(-x)=1/e^x)
#include <stdio.h>
#include <math.h>
double Exp(double x);
int main(void)
{
double x;
printf("x=");
scanf("%le", &x);
printf("%le", Exp(x));
return 0;
}
double Exp(double x)
{
double h, eps = 1.e-16, Sum = 1.0;
int i = 2;
h = x;
do
{
Sum += h;
h *= x / i;
i++;
} while (fabs(h) > eps);
return Sum ;
}
For example:
x=-40 the value is 4.24835e-18 but programm gives me 3.116952e-01.The absolute error is ~0.311
x=-50 the value is 1.92875e-22 programm gives me 2.041833e+03.The absolute error is ~2041.833
The problem is caused by rounding errors at the middle phase of the algorithm.
The h is growing quickly as 40/2 * 40/3 * 40 / 4 * ... and oscillating in sign. The values for i, h and Sum for x=-40 for consecutive iterations can be found below (some data points omitted for brevity):
x=-40
i=2 h=800 Sum=-39
i=3 h=-10666.7 Sum=761
i=4 h=106667 Sum=-9905.67
i=5 h=-853333 Sum=96761
i=6 h=5.68889e+06 Sum=-756572
...
i=37 h=-1.37241e+16 Sum=6.63949e+15
i=38 h=1.44464e+16 Sum=-7.08457e+15
i=39 h=-1.48168e+16 Sum=7.36181e+15
i=40 h=1.48168e+16 Sum=-7.45499e+15
i=41 h=-1.44554e+16 Sum=7.36181e+15
i=42 h=1.37671e+16 Sum=-7.09361e+15
i=43 h=-1.28066e+16 Sum=6.67346e+15
i=44 h=1.16423e+16 Sum=-6.13311e+15
i=45 h=-1.03487e+16 Sum=5.50923e+15
i=46 h=8.99891e+15 Sum=-4.83952e+15
...
i=97 h=-2610.22 Sum=1852.36
i=98 h=1065.4 Sum=-757.861
i=99 h=-430.463 Sum=307.534
...
i=138 h=1.75514e-16 Sum=0.311695
i=139 h=-5.05076e-17 Sum=0.311695
3.116952e-01
The peak magnitude of sum is 7e15. This is where the precision is lost. Type double can be represented with about 1e-16 accuracy. This gives expected absolute error of about 0.1 - 1.
As the expected sum (value of exp(-40) is close to zero the final absolute error is close to the maximal absolute error of the partial sums.
For x=-50 the peak value of sum is 1.5e20 what gives the absolute error due to finite representation of double at about 1e3 - 1e4 what is close to observed one.
Not much can be fixed without significant changes to algorithm to avoid forming those partial sums. Alternatively, compute exp(-x) as 1/exp(x).
For negative x, adding the alternating +/- terms creates a computational problems even in the first sum of 1.0 + x as the final sum error can be expected to be as bad as the least significant bit of 1.0 or about 1 part in 1016. This implies x_min as in Exp(x_min) == 1.0e-16 is the minimum useful computational value (e.g. x about -36)
A simple solution is to form a good Exp(positive_x) and for negative values ...
double Exp(double x) {
if (x < 0) {
return 1.0 / Exp(-x);
}
...
A good (and simple) Exp(positive_x) computes terms until a term + 1.0 is still 1.0 as additional small terms do not change the sum significantly. Works well for all x (very small error) except could use improvements when the result should be a sub-normal.
double my_exp(double x) {
if (x < 0) {
return 1.0 / my_exp(-x);
}
double sum = 1.0;
unsigned n = 1;
double term = 1.0;
do {
term *= x / n++;
sum += term;
if (!isfinite(term)) {
return term;
}
} while (1.0 != term + 1.0);
return sum;
}
Im trying to calculate the sin(x) using Taylor Series for sin x by formula with the accuracy of 0.00001 (meaning until the sum goes lower than precision of 0.00001).
(x is given by radians).
The problem is that my function to calculate sin (using Taylor series formula) is printing out the same value as given (for example if 7 is given it will print out 7.00000 instead of 0.656987).
tried to debug my code using gdb and couldnt figure out why it stops after first iteration.
Here`s my C code in order to calculate sin (x ) using Taylor series.
double my_sin(double x) {
int i=3,sign=1; // sign variable is meant to be used for - and + operator inside loop.
// i variable will be used for power and factorial division
double sum=x, accuracy=0.000001; // sum is set for first x.
for(i=3;fabs(sum) < accuracy ;i+=2){ // starting from power of 3.
sign*=-1; // sign will change each iteration from - to +.
sum+=sign*(pow(x,i)/factorial(i)); // the formula itself (factorial simple function for division)
}
return (sum);
}
Any help would be appreciated.
Thanks
tried to debug my code using gdb and couldnt figure out why it stops after first iteration.
Well, let's do it again, step by step.
sum = x (input is 7.0, so sum == 7.0).
for(i=3; fabs(sum) < accuracy; i+=2) { ...
Since sum is 7.0, it is not less than accuracy, so the loop body never executes.
return sum; -- sum is still 7.0, so that's what your function returns.
Your program does exactly what you asked it to do.
P.S. Here the code you probably intended to write:
double my_sin(double x) {
double sum = x, accuracy = 0.000001;
double delta = DBL_MAX;
for(int i = 3, sign = -1; accuracy < fabs(delta); i += 2, sign = -sign) {
delta = sign * pow(x, i) / factorial(i);
sum += delta;
}
return sum;
}
I'm working on a project for our school and we are required to create a program that computes the approximation of the Taylor Expansion Series of sin x and cos x, only using <stdio.h> and without user-defined functions other than int main(), of all angles from -180 to 180 in increments of +5. the following is my code:
#include <stdio.h>
#define PI 3.141592653589
#define NUMBER_OF_TERMS 10
int
main()
{
int cosctr, sinctr;
double ctr, radi;
double cosaccu, costerm, sinaccu, sinterm;
for (ctr = -180; ctr < 185; ctr = ctr + 5) {
radi = ctr * PI/180.0;
cosctr = 1;
cosaccu = 1;
costerm = 1;
sinctr = 2;
sinaccu = radi;
sinterm = radi;
while (cosctr <= 2*NUMBER_OF_TERMS) {
costerm = costerm*(-1)*(radi*radi)/(cosctr*(cosctr + 1));
cosaccu = cosaccu + costerm;
cosctr+=2;
} do {
sinterm = sinterm*(-1)*(radi*radi)/(sinctr*(sinctr + 1));
sinaccu = sinaccu + sinterm;
sinctr+=2;
} while (sinctr <= 2*NUMBER_OF_TERMS);
printf("%.2lf %.12lf %.12lf %.12lf\n", ctr, radi, cosaccu, sinaccu);
} return 0;
}
The code above is accurate for a 15 terms expansion approximation. however, if I change NUMBER_OF_TERMS to, for example, 5 or 10, the approximation is flawed.
Any suggestions?
Let me clarify: I need to obtain an approximation of 5 terms, 10 terms, and 15 terms. I cannot use any other library other than <stdio.h>. I cannot use any other functions outside of int main() (I apologize for the vagueness of my explanation before).
Please answer with the included corrected code.
The key to high precession, yet simple calculation of sind(degrees) and cosd(degrees) is to reduce the range of degree to 0 to 90 first (or even 0 to 45), using the usual trigonometric adjustments with degree arrangements first.
Reductions:
angle = fmod(angle, 360) // reduce (-360..360) or use a = a - (int)(a/360)
sin(x) = -sin(-x) // reduce to [0..360)
cos(x) = cos(-x) // reduce to [0..360)
sin(x) = -sin(x-180) // reduce to [0..180)
cos(x) = -cos(x-180) // reduce to [0..180)
sin(x) = cos(90-x) // reduce to [0..90)
Further reductions:
For [45-90) use sin(x) = cos(90-x) // reduce to [0..45)
then convert to radians and use Taylor series expansion.
Example
Note: Since code is dealing with double, typically 17 digits of precision, no need to use a course PI approximation.
// #define PI 3.141592653589
#define PI 3.1415926535897932384626433832795
I tried your code; it works fine for me, in that it does what it looks like it's designed to do. Here's a comparison between your code's output for the cosine at 5 and 10 terms and the same approximation as calculated by Mathematica. They agree up to <10^-12, i.e. your outputted precision.:
The only problem I see with your code is that, with the way you designed your loops, you're actually taking into account NUMBER_OF_TERMS + 1 terms if you count the first terms in the expansion (i.e. the constant term for the cosine, the linear term for the sine.) You start with this first term, and then your loop adds another NUMMBER_OF_TERMS terms. If that is not by design, you're actually approximating the functions with higher precision that you are expecting.
By its very definition, a Taylor series is a summation of an infinite series of terms.
Thus, a Taylor finite expansion only is an approximation of the true result: as the number of terms increases, the accuracy of this approximation improves.
If there are enough terms, the approximation error at some point becomes unnoticeable. However, if you try lowering the number of terms, the approximation error increases and can detected.
In your case, the approximation error is below the detection threshold for NUMBER_OF_TERMS= 15, but becomes noticeable when NUMBER_OF_TERMS= 10 or less.
The Taylor expansions of sin(x) and cos(x) takes longer to converge as x increases. But since these are periodic functions, you don't actually need to bother expanding the series for values outside the range 0-90°.
For values of x outside this range, use the following identities:
sin(x) = -sin(x+180°) = -sin(-x) = sin(180°-x)
cos(x) = -cos(x+180°) = cos(-x) = -cos(180°-x)
For example, sin(175°) = sin(5°), cos(-120°) = -cos(60°)
i figured it out with help from another user.
turns out i was calculating terms + 1, making the answer more accurate than intended. after 15 terms the changes are past the 12th decimal point, and therefore did not show on the results.
#include <stdio.h>
#define PI 3.141592653589
#define NUMBER_OF_TERMS 10 // 5 and 15 work as well
int
main()
{
int cosctr, sinctr;
double ctr, radi;
double cosaccu, costerm, sinaccu, sinterm; // accu will be final answer, term will be added to accu
for (ctr = -180; ctr < 185; ctr+=5) { // for loop; ctr initialized at -185 and added to in increments of 5 to produce degrees
radi = ctr * PI/180.0; // calculation for radians (assigned to radi)
cosctr = 1; // initialization for cos counter; must be included in loop to allow correct calculations of cos
cosaccu = 1; // first term is 1
costerm = 1; // base term, to be multiplied with termcalc formula
sinctr = 2; // initialization for sin counter; must be included in loop to allow correct calculations of sin
sinaccu = radi; // first term is x, or degrees in radians (radi)
sinterm = radi; // base term for sin
// cos calculation
while (cosctr < 2*NUMBER_OF_TERMS-1) { // accuracy check, 2* since increments of 2; NOTE: actual values are (1, 3, 5,...)
costerm = costerm*(-1)*(radi*radi)/(cosctr*(cosctr + 1)); // TERMCALC FORMULA; multiplying previous term with formula creates next term
cosaccu = cosaccu + costerm; // addition of new term to previous sum; dependent on accuracy (NUMBER_OF_TERMS)
cosctr+=2;
} do { // sin calculation; identical to cos, albeit with substituted vars
sinterm = sinterm*(-1)*(radi*radi)/(sinctr*(sinctr + 1));
sinaccu = sinaccu + sinterm;
sinctr+=2;
} while (sinctr < 2*NUMBER_OF_TERMS-1); // accuracy check, 2* since increments of 2; NOTE: actual values are (2, 4, 6,...)
printf("%.2lf\t%.12lf\t%.12lf\t%.12lf\n", ctr, radi, cosaccu, sinaccu); // final display; /t used for convenience
} return 0; // finally!!!
}
I'm working on a project for our school and we are required to create a program that computes the approximation of the Taylor Expansion Series of sin x and cos x, only using <stdio.h> and without user-defined functions other than int main(), of all angles from -180 to 180 in increments of +5. the following is my code:
#include <stdio.h>
#define PI 3.141592653589
#define NUMBER_OF_TERMS 10
int
main()
{
int cosctr, sinctr;
double ctr, radi;
double cosaccu, costerm, sinaccu, sinterm;
for (ctr = -180; ctr < 185; ctr = ctr + 5) {
radi = ctr * PI/180.0;
cosctr = 1;
cosaccu = 1;
costerm = 1;
sinctr = 2;
sinaccu = radi;
sinterm = radi;
while (cosctr <= 2*NUMBER_OF_TERMS) {
costerm = costerm*(-1)*(radi*radi)/(cosctr*(cosctr + 1));
cosaccu = cosaccu + costerm;
cosctr+=2;
} do {
sinterm = sinterm*(-1)*(radi*radi)/(sinctr*(sinctr + 1));
sinaccu = sinaccu + sinterm;
sinctr+=2;
} while (sinctr <= 2*NUMBER_OF_TERMS);
printf("%.2lf %.12lf %.12lf %.12lf\n", ctr, radi, cosaccu, sinaccu);
} return 0;
}
The code above is accurate for a 15 terms expansion approximation. however, if I change NUMBER_OF_TERMS to, for example, 5 or 10, the approximation is flawed.
Any suggestions?
Let me clarify: I need to obtain an approximation of 5 terms, 10 terms, and 15 terms. I cannot use any other library other than <stdio.h>. I cannot use any other functions outside of int main() (I apologize for the vagueness of my explanation before).
Please answer with the included corrected code.
The key to high precession, yet simple calculation of sind(degrees) and cosd(degrees) is to reduce the range of degree to 0 to 90 first (or even 0 to 45), using the usual trigonometric adjustments with degree arrangements first.
Reductions:
angle = fmod(angle, 360) // reduce (-360..360) or use a = a - (int)(a/360)
sin(x) = -sin(-x) // reduce to [0..360)
cos(x) = cos(-x) // reduce to [0..360)
sin(x) = -sin(x-180) // reduce to [0..180)
cos(x) = -cos(x-180) // reduce to [0..180)
sin(x) = cos(90-x) // reduce to [0..90)
Further reductions:
For [45-90) use sin(x) = cos(90-x) // reduce to [0..45)
then convert to radians and use Taylor series expansion.
Example
Note: Since code is dealing with double, typically 17 digits of precision, no need to use a course PI approximation.
// #define PI 3.141592653589
#define PI 3.1415926535897932384626433832795
I tried your code; it works fine for me, in that it does what it looks like it's designed to do. Here's a comparison between your code's output for the cosine at 5 and 10 terms and the same approximation as calculated by Mathematica. They agree up to <10^-12, i.e. your outputted precision.:
The only problem I see with your code is that, with the way you designed your loops, you're actually taking into account NUMBER_OF_TERMS + 1 terms if you count the first terms in the expansion (i.e. the constant term for the cosine, the linear term for the sine.) You start with this first term, and then your loop adds another NUMMBER_OF_TERMS terms. If that is not by design, you're actually approximating the functions with higher precision that you are expecting.
By its very definition, a Taylor series is a summation of an infinite series of terms.
Thus, a Taylor finite expansion only is an approximation of the true result: as the number of terms increases, the accuracy of this approximation improves.
If there are enough terms, the approximation error at some point becomes unnoticeable. However, if you try lowering the number of terms, the approximation error increases and can detected.
In your case, the approximation error is below the detection threshold for NUMBER_OF_TERMS= 15, but becomes noticeable when NUMBER_OF_TERMS= 10 or less.
The Taylor expansions of sin(x) and cos(x) takes longer to converge as x increases. But since these are periodic functions, you don't actually need to bother expanding the series for values outside the range 0-90°.
For values of x outside this range, use the following identities:
sin(x) = -sin(x+180°) = -sin(-x) = sin(180°-x)
cos(x) = -cos(x+180°) = cos(-x) = -cos(180°-x)
For example, sin(175°) = sin(5°), cos(-120°) = -cos(60°)
i figured it out with help from another user.
turns out i was calculating terms + 1, making the answer more accurate than intended. after 15 terms the changes are past the 12th decimal point, and therefore did not show on the results.
#include <stdio.h>
#define PI 3.141592653589
#define NUMBER_OF_TERMS 10 // 5 and 15 work as well
int
main()
{
int cosctr, sinctr;
double ctr, radi;
double cosaccu, costerm, sinaccu, sinterm; // accu will be final answer, term will be added to accu
for (ctr = -180; ctr < 185; ctr+=5) { // for loop; ctr initialized at -185 and added to in increments of 5 to produce degrees
radi = ctr * PI/180.0; // calculation for radians (assigned to radi)
cosctr = 1; // initialization for cos counter; must be included in loop to allow correct calculations of cos
cosaccu = 1; // first term is 1
costerm = 1; // base term, to be multiplied with termcalc formula
sinctr = 2; // initialization for sin counter; must be included in loop to allow correct calculations of sin
sinaccu = radi; // first term is x, or degrees in radians (radi)
sinterm = radi; // base term for sin
// cos calculation
while (cosctr < 2*NUMBER_OF_TERMS-1) { // accuracy check, 2* since increments of 2; NOTE: actual values are (1, 3, 5,...)
costerm = costerm*(-1)*(radi*radi)/(cosctr*(cosctr + 1)); // TERMCALC FORMULA; multiplying previous term with formula creates next term
cosaccu = cosaccu + costerm; // addition of new term to previous sum; dependent on accuracy (NUMBER_OF_TERMS)
cosctr+=2;
} do { // sin calculation; identical to cos, albeit with substituted vars
sinterm = sinterm*(-1)*(radi*radi)/(sinctr*(sinctr + 1));
sinaccu = sinaccu + sinterm;
sinctr+=2;
} while (sinctr < 2*NUMBER_OF_TERMS-1); // accuracy check, 2* since increments of 2; NOTE: actual values are (2, 4, 6,...)
printf("%.2lf\t%.12lf\t%.12lf\t%.12lf\n", ctr, radi, cosaccu, sinaccu); // final display; /t used for convenience
} return 0; // finally!!!
}
I wrote a code for calculating sin using its maclaurin series and it works but when I try to calculate it for large x values and try to offset it by giving a large order N (the length of the sum) - eventually it overflows and doesn't give me correct results. This is the code and I would like to know is there an additional way to optimize it so it works for large x values too (it already works great for small x values and really big N values).
Here is the code:
long double calcMaclaurinPolynom(double x, int N){
long double result = 0;
long double atzeretCounter = 2;
int sign = 1;
long double fraction = x;
for (int i = 0; i <= N; i++)
{
result += sign*fraction;
sign = sign*(-1);
fraction = fraction*((x*x) / ((atzeretCounter)*(atzeretCounter + 1)));
atzeretCounter += 2;
}
return result;
}
The major issue is using the series outside its range where it well converges.
As OP said "converted x to radX = (x*PI)/180" indicates the OP is starting with degrees rather than radians, the OP is in luck. The first step in finding my_sin(x) is range reduction. When starting with degrees, the reduction is exact. So reduce the range before converting to radians.
long double calcMaclaurinPolynom(double x /* degrees */, int N){
// Reduce to range -360 to 360
// This reduction is exact, no round-off error
x = fmod(x, 360);
// Reduce to range -180 to 180
if (x >= 180) {
x -= 180;
x = -x;
} else if (x <= -180) {
x += 180;
x = -x;
}
// Reduce to range -90 to 90
if (x >= 90) {
x = 180 - x;
} else if (x <= -90) {
x = -180 - x;
}
//now convert to radians.
x = x*PI/180;
// continue with regular code
Alternative, if using C11, use remquo(). Search SO for sample code.
As #user3386109 commented above, no need to "convert back to degrees".
[Edit]
With typical summation series, summing the least significant terms first improves the precision of the answer. With OP's code this can be done with
for (int i = N; i >= 0; i--)
Alternatively, rather than iterating a fixed number of times, loop until the term has no significance to the sum. The following uses recursion to sum the least significant terms first. With range reduction in the -90 to 90 range, the number of iterations is not excessive.
static double sin_d_helper(double term, double xx, unsigned i) {
if (1.0 + term == 1.0)
return term;
return term - sin_d_helper(term * xx / ((i + 1) * (i + 2)), xx, i + 2);
}
#include <math.h>
double sin_d(double x_degrees) {
// range reduction and d --> r conversion from above
double x_radians = ...
return x_radians * sin_d_helper(1.0, x_radians * x_radians, 1);
}
You can avoid the sign variable by incorporating it into the fraction update as in (-x*x).
With your algorithm you do not have problems with integer overflow in the factorials.
As soon as x*x < (2*k)*(2*k+1) the error - assuming exact evaluation - is bounded by abs(fraction), i.e., the size of the next term in the series.
For large x the biggest source for errors is truncation resp. floating point errors that are magnified via cancellation of the terms of the alternating series. For k about x/2 the terms around the k-th term have the biggest size and have to be offset by other big terms.
Halving-and-Squaring
One easy method to deal with large x without using the value of pi is to employ the trigonometric theorems where
sin(2*x)=2*sin(x)*cos(x)
cos(2*x)=2*cos(x)^2-1=cos(x)^2-sin(x)^2
and first reduce x by halving, simultaneously evaluating the Maclaurin series for sin(x/2^n) and cos(x/2^n) and then employ trigonometric squaring (literal squaring as complex numbers cos(x)+i*sin(x)) to recover the values for the original argument.
cos(x/2^(n-1)) = cos(x/2^n)^2-sin(x/2^n)^2
sin(x/2^(n-1)) = 2*sin(x/2^n)*cos(x/2^n)
then
cos(x/2^(n-2)) = cos(x/2^(n-1))^2-sin(x/2^(n-1))^2
sin(x/2^(n-2)) = 2*sin(x/2^(n-1))*cos(x/2^(n-1))
etc.
See https://stackoverflow.com/a/22791396/3088138 for the simultaneous computation of sin and cos values, then encapsulate it with
def CosSinForLargerX(x,n):
k=0
while abs(x)>1:
k+=1; x/=2
c,s = getCosSin(x,n)
r2=0
for i in range(k):
s2=s*s; c2=c*c; r2=s2+c2
s = 2*c*s
c = c2-s2
return c/r2,s/r2