Implement Sine in Java without Math.sin function

2019-09-16 18:15发布

问题:

I'm trying to implement the sine function in Java without using Math.sin(x). So I'm trying to realize this with the Taylor series. Unfortunately, this code gives the wrong result(s).

If you don't know what the Taylor series is, have a look:

Here's the code snippet I created:

public static double sin(double a) {
   double temp = 1;
   int denominator = -1;
   if(a == Double.NEGATIVE_INFINITY || !(a < Double.POSITIVE_INFINITY)) {
      return Double.NaN;
   } 
   if(a != 0) {
      for (int i = 0; i <= a; i++) {
         denominator += 2;
         if(i % 2 == 0) {
            temp = temp + (Math.pow(a, denominator) / Factorial.factorial(denominator));
         } else {
            temp = temp - (Math.pow(a, denominator) / Factorial.factorial(denominator));
         }
      }
   }
   return temp;
}

I can't find the mistake I did. Do you?

回答1:

There are two main issues in your code. First issue is that you are looping i from 0 to a. This means that, if a is a negative value, the for loop does not even start and your result will always be 1.0. Whereas if a is positive, the loop starts, but it stops after (int) a iterations, and it doesn't make much sense, since the Taylor apporximation works fine when iterations n tends to infinity.

Second major issue is you are not making enough controls on the input value a. As I already said in Python: Calculate sine/cosine with a precision of up to 1 million digits

The real Taylor expansion centered in x0 is:

where Rn is the Lagrange Remainder

Note that Rn grows fast as soon as x moves away from the center x0.

Since you are implementing the Maclaurin series (Taylor series centered in 0) and not the general Taylor series, your function will give really wrong results when trying to calculate sin(x) for big values of x.

So before the for loop you must reduce the domain to at least [-pi, pi]... better if you reduce it to [0, pi] and take advantage of sine's parity.

Working code:

public static double sin(double a) {

    if (a == Double.NEGATIVE_INFINITY || !(a < Double.POSITIVE_INFINITY)) {
        return Double.NaN;
    }

    // If you can't use Math.PI neither,
    // you'll have to create your own PI
    final double PI = 3.14159265358979323846;

    // Fix the domain for a...

    // Sine is a periodic function with period = 2*PI
    a %= 2 * PI;
    // Any negative angle can be brought back
    // to it's equivalent positive angle
    if (a < 0) {
        a = 2 * PI - a;
    }
    // Also sine is an odd function...
    // let's take advantage of it.
    int sign = 1;
    if (a > PI) {
        a -= PI;
        sign = -1;
    }
    // Now a is in range [0, pi].


    // Calculate sin(a)

    // Set precision to fit your needs.
    // Note that 171! > Double.MAX_VALUE, so
    // don't set PRECISION to anything greater
    // than 84 unless you are sure your
    // Factorial.factorial() can handle it
    final int PRECISION = 50;
    double temp = 0;
    for (int i = 0; i <= PRECISION; i++) {
        temp += Math.pow(-1, i) * (Math.pow(a, 2 * i + 1) / Factorial.factorial(2 * i + 1));
    }

    return sign * temp;

}


回答2:

Your issue is that you are using the value that is to be evaluated for the sine function as the limit for the denominator. A Taylor series is evaluated as the limit of the function approaches infinity. You are only evaluating it in this case to the size of the input value, which doesn't really make sense. You should replace your for loop comparison with i < x where x is a constant representing however precise you wish to make it (the function will be fairly accurate for a value as low as 20 or so).