Sine of an angle can be approximated using the following series:

where x is the angle in radians.

The above question had been asked in ISC(12) once and the method used by students usually use nested loops. Outer loop iterates for a certain no. of terms and inner loop calculates factorial. The code based on the above logic is as follows:

class Sine{ public static float sine1(float x){ float sin=0.0f; long fact; for(int i=1; i<=10; i++){ fact=1; for(int j=1; j<=2*i-1; j++){ fact=fact*j; } if(i%2==1){ sin=sin+(float)(Math.pow(x,2*i-1)/fact); }else{ sin=sin-(float)(Math.pow(x,2*i-1)/fact); } } return sin; } public static void main(String args[]){ System.out.println("Sin(1.57079632679) is "+Math.sin(1.57079632679f)+" "+sine1(1.57079632679f)); } }

The above program displays the sine obtained from Math.sin() as well as out own sine1() method. The important point over here is that each successive term is smaller than the previous term because the denominator is increasing at a higher rate than the numerator. In the above program the outer loop is running up to 10 and is responsible for the accuracy of the sine we are calculating—the more the number of terms the better the approximation, however, don’t be under the impression that a very large number of iteration will give you a very accurate answer because we are calculating factorial inside the loop and factorials of large numbers won’t fit in an integer variable or for that matter into long as well.

The above method will give correct answer which would obviously be approximate due to the nature of the problem. Now let us see how we can improve the above solution. The accuracy in the above solution is by trial and error and we don’t know for sure the accuracy in terms of number of decimal places. Let’s we want the answer to be accurate to five decimal places that is 0.00001. This is easy to implement we can keep on computing new terrms till the value of the term is more than 0.00001 and once the value of the term falls below 0.00001 we can abort the loop. The basic idea is that a value of less than 0.00001 will not cause significant difference in the answer.

The term of the above series are being alternatively added and subtracted to the answer. Notice that at any stage the next numerator can be obtained by multiplying the current numerator by the square of x (that is x*x) and the next denominator can be obtained by multiplying the current denominator by (denominator+1)* (denominator+2). Let us see this with the help of an example—first term is x, multiplying by x^{2} will give us x^{3}, which is the numerator of the second term; now the denominator of the first term is 1, multiplying 1 by (1+1) and (1+2) will give us 6 (1*2*3=6). This way, just by multiplying the numerator and the denominator, we would be able to save the time spent in calculating the factorial as well as some time spent due call of Math.pow(). The program after incorporating the above logic would be as follows:

class Sine{ public static float sine1(float x){ float sin=0.0f; long fact; for(int i=1; i<=10; i++){ fact=1; for(int j=1; j<=2*i-1; j++){ fact=fact*j; } if(i%2==1){ sin=sin+(float)(Math.pow(x,2*i-1)/fact); }else{ sin=sin-(float)(Math.pow(x,2*i-1)/fact); } } return sin; } public static float sine2(float x){ float sin=x,term, numerator=x, denominator=1, xsquare=x*x, factorial=1, sign=-1; do{ numerator *= xsquare; denominator=denominator*(factorial+1)*(factorial+2); factorial=factorial+2; term=numerator/denominator; sin=sin+(sign*term); sign*=-1; }while(term>0.00001); return sin; } public static void main(String args[]){ System.out.println("Sin(1.57079632679) is "+Math.sin(1.57079632679f)+" "+sine1(1.57079632679f)+" "+sine2(1.57079632679f)); } }

Note that now we have added one more function sine2() which employs the new logic. In order to alternate the terms between negative and positive we have taken an integer variable sign initialized to -1. The value of the variable sign will become +1 after being multiplied by -1 one and again -1 on next multiplication by -1. Due to this the value of the variable term will also alternate from negative to positive and positive to negative.

Let us also benchmark out programs so that we can actually see how efficient is our two sine function with respect to Math.sin. For details of how to determine the time taken for some piece of code one can see my earlier post Timing ‘Prime time’. The following program will call functions Math.sin(), sine1() and sine(2) and display the time taken to evaluate the sine of various angle in multiple of π/2:

class Sine{ public static float sine1(float x){ float sin=0.0f; long fact; for(int i=1; i<=10; i++){ fact=1; for(int j=1; j<=2*i-1; j++){ fact=fact*j; } if(i%2==1){ sin=sin+(float)(Math.pow(x,2*i-1)/fact); }else{ sin=sin-(float)(Math.pow(x,2*i-1)/fact); } } return sin; } public static float sine2(float x){ float sin=x,term, numerator=x, denominator=1, xsquare=x*x, factorial=1, sign=-1; do{ numerator *= xsquare; denominator=denominator*(factorial+1)*(factorial+2); factorial=factorial+2; term=numerator/denominator; sin=sin+(sign*term); sign*=-1; }while(term>0.00001); return sin; } public static void benchmark(){ long startTime,estimatedTime; float sineValue; float testcase[]={1.5707963268f, 3.1415926536f, 4.7123889804f, 6.2831853072f, 7.853981634f, 9.4247779607f, 10.9955742875f, 12.5663706143f, 14.1371669411f}; System.out.println("n\t\tMath.sin\tsine1\t\tsine2"); for(int i=0; i<testcase.length; i++){ System.out.print(testcase[i]+"\t"); startTime= System.nanoTime(); sineValue=(float)Math.sin(testcase[i]); estimatedTime = System.nanoTime()- startTime; System.out.print(estimatedTime); startTime= System.nanoTime(); sineValue=sine1(testcase[i]); estimatedTime = System.nanoTime()- startTime; System.out.print("\t\t"+estimatedTime); startTime= System.nanoTime(); sineValue=sine2(testcase[i]); estimatedTime = System.nanoTime()- startTime; System.out.println("\t\t"+estimatedTime); } } public static void main(String args[]){ benchmark(); } }

The output of the benchmark() function will vary from system to system and on my system it’s as follows:

n | Math.sin | sine1 | sine2 |

1.5707964 | 2341 | 10698 | 3408 |

3.1415927 | 2007 | 10541 | 3635 |

4.712389 | 1628 | 10554 | 3899 |

6.2831855 | 1620 | 10390 | 4229 |

7.8539815 | 1657 | 10423 | 4339 |

9.424778 | 1684 | 10700 | 5329 |

10.995574 | 1807 | 10450 | 5436 |

12.566371 | 2533 | 6023 | 3408 |

14.137167 | 1492 | 6799 | 4282 |

As usual, the main function has purposely been kept to minimum and its expected that students will write appropriate input/output statements as per the requirement.