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?
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;
}
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).