Vaibhav
Vaibhav

Reputation: 131

Sine Function without any library

I am new at learning c++. Because of curiosity, I created a sine function without using any library (except iostream, to take input and output). Here is the code that I wrote, and it works perfectly.

    #include<iostream>
using namespace std;
int factorial(int n);
double sin(double x, int n);
double pow(double x, int n);
int main(){
    double num;
    int n;
    cout << "Enter any Number" << endl;
    cin >> num;
    cout << "Enter n" <<  endl;
    cin >> n;
    cout << "sine of given x equals " << sin(num, n);
}
int factorial(int n){
    int a=1;
    for(int p=1; p<=n; p++){
        a=a*p;
    }
        return a;
}
double sin(double x, int n){
    double sine=0;
    for ( int a=1, b=1; a<n+n+1; a+=2, b++){
        sine=sine + (pow(-1, b+1))*(pow(x,a))/(factorial(a));
    }
    return sine;
}
double pow(double x, int n){
    double num=1;
    for (int a=1; a<=n; a++){
        num=num*x;
    }
    return num;
}

It uses taylor series to calculate sine. I also takes 'n' as number of terms to include from taylor series to improve accuracy. I have certain doubts in this

1) the sin function i created, I found by trial and error that in the for loop, I have to write 'a< n+n+1' , but If I tried to write 'a<2n+1' it gives me ugly compilation error. Why is it so? What can I do to make it that way?

2)If I try to enter large values of n (>15-16) it gives answer as 'nan'. Why is it so? I think double has huge capacity of storing numbers (10^408). Correct me If I'm wrong. Or what can be done to make it calculate for huge value of n.

3) I know the code I written is ugly, I don't want to use any library functions. Anyway what can I do to make this code do better in terms of algorithm. Is there any other efficient way without using any library.

4) Any other comments/hints/tips for learning more in future?

Upvotes: 2

Views: 7132

Answers (4)

Anılcan Gulkaya
Anılcan Gulkaya

Reputation: 1

Here are my examples from Wikipedia using Taylor expansion, I think these are the fastest possible. you can delete last lines that contains t += ... for low precision and high performance:

#define _USE_MATH_DEFINES
#include <math.h>
inline float Sin(float x) 
{
    // if x is in between -pi and pi no need to fmod
    //x = fmodf(x + M_PI, 2.0f * M_PI) - M_PI; 
    float xx = x * x * x; // x^3
    float t = x - (xx  / 6.0f); // 6 = !3
    t += (xx *= x * x) / 120.0f; // 120 = !5
    t -= (xx *= x * x) / 5040.0f; // 5040 = !7 
    t += (xx * x * x)  / 362880.0f; // 362880 = !9 
    return t;
}

inline float Cos(float x) 
{
    //x = fmodf(x + M_PI, 2.0f * M_PI) - M_PI;
    float xx = x * x; // x^2
    float t = 1.0f - (xx / 2.0f); // 2 = !2
    t += (xx *= x * x) / 24.0f;  // 24.0f = !4
    t -= (xx *= x * x) / 720.0f; // 720.0f = !6
    t += (xx * x * x) / 40320.0f; // 40320 = !8
    return t;
}

edit: you can find my other trigonometric functions here: github link

Upvotes: 0

R Sahu
R Sahu

Reputation: 206577

  1. Use a < 2*n +1.

  2. factorial of 15 is 1,307,674,368,000. That number cannot be represented by an int. You have to rethink the code you use to compute the terms of the Taylor series for sine.

  3. It will be better to post your working code at http://codereview.stackexchange.com to get feedback on how to improve your code.

N-th term in the Taylor series expansion is (-1)^(N-1)x^(2*N+1)/(2*N+1)!
(N+1)-th term is (-1)^(N)x^(2*(N+1)+1)/(2*(N+1)+1)!

T(N+1) = -1*T(N)*x^2/((2*(N+1)+1)*(2*(N+1))

When you use this logic, you don't need to need pow or factorial. You derive the (N+t)-th term from the N-th term very easily. The starting point, T(0) is simply x.

Here's an example program:

#include <iostream>
#include <iomanip>

using namespace std;

double sin(double x, int n)
{
   double t = x;
   double sine = t;
   for ( int a=1; a<n; ++a)
   {
      double mult = -x*x/((2*a+1)*(2*a));
      t *= mult;
      sine += t;
   }
   return sine;
}

int main()
{
    double num;
    int n;
    cout << "Enter any Number" << endl;
    cin >> num;
    cout << "Enter n" <<  endl;
    cin >> n;
    cout << std::setprecision(20) << "sine of given x equals " << sin(num, n) << std::endl;

   return 0;
}

Sample input:

3.5
5

Output:

sine of given x equals -0.32838899588879211233

Sample input:

3.5
20

Output:

sine of given x equals -0.35078322768961955891

Sample input:

3.5
200

Output:

sine of given x equals -0.35078322768961955891

P.S. There is no change in output when N is changed from 20 to 200.

Upvotes: 6

Peter
Peter

Reputation: 36597

You would be hard pressed to convince anyone of your claim it "works perfectly", particularly since you go on to describe problems.

1) is a basic misunderstanding on your part with expressions. 2n+1 does not double n and add 1 to it, in C++. The correct expression is (presumably) 2*n + 1. Go back and read any elementary introduction to C++ to understand why.

2) Your method of computing sin() raises a value to a large power. If you're raising a value to the 16th power, it doesn't take a particularly large value to overflow most floating point representations i.e. produce a value larger than the floating point type can represent. The result of that is undefined in C++, but with IEEE representations the result is a NaN.

2a) A problem you have missed is that factorial() grows very fast. A 16-bit signed int (which corresponds to the minimum required) will overflow when calculating 8!. A 32 bit signed integer (common in practice, but not guaranteed) will overflow when computing 13!. Even a 64-bit signed int (which does exist with some compilers) will overflow when calculating 21!. The result of overflowing a (built-in) signed integral type is undefined behaviour. Picking a larger sized integer won't address this, since factorial grows faster for larger values - not that changing from a 32-bit integer to a 64-bit integer only improves ability to calculate factorials between 14 and 20.

3) The big problem is that you are expecting to be able to compute big values (which overflow variables), divide them, and get a mathematically sane result. Computers don't work that way. And remove from your consciousness any concept that using your pow() or factorial() is required - if either of those overflow, dividing their results is pointless (and, in your case, both probably are overflowing). What you need to do is look at the consecutive terms in the Taylor series, and exploit mathematical relationships between them.

The first term in the Taylor's series (when computing sin(x)) is x. The second is -x^3/3! [using ^ to represent "to the power of", although that is not valid C++]. The third is +x^5/5!. The fourth is -x^7/7!. Consider what the relationship is between the first and second terms. Then consider what the relationship is between the second and third terms. Generalise that, and you will have a method to compute sin() using the Taylors series with significantly reduced risk of overflowing something.

And, if anyone tried to tell you to use a "huge integer" (an integral type that has no upper bound) and a "huge floating point type", ignore them. Yes, those things have their place, but you are better off avoiding the need for them if you can. It is possible to avoid that need in this case.

Upvotes: 3

STF
STF

Reputation: 1513

1) You need to write

 a < 2*n+1 

You can use recursion functions to do the code more nice and clear.

Upvotes: 3

Related Questions