2

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;
}
njuffa
  • 23,970
  • 4
  • 78
  • 130
scifie
  • 65
  • 1
  • 2
  • 14
  • 5
    The best way to make it work for large values of `x` is to adjust `x` by multiples of 2PI, so that `x` is in the range -PI to PI. – user3386109 Mar 22 '15 at 22:08
  • 3
    Not answering your question but just in case you are interested, the standard way to compute a sin(x) function that works for large values of x is to have a first **argument reduction** phase followed by a **polynomial approximation** phase (for which the coefficient of the MacLaurin are not optimal, but that's a story for another time). http://www.csee.umbc.edu/~phatak/645/supl/Ng-ArgReduction.pdf – Pascal Cuoq Mar 22 '15 at 22:11
  • @user3386109, you mean pi=180 not pi=3.14 right? but i have problem even with numbers like x=44 - using pronts i found that the problem isn't overflow but the problem is that when I use large N values to offset the x eventually the factorial grows so mush that fraction=0 and stops improving the result and bringing it closer to the real value and because the original value x was so large to begin with that the result is to is stack on a large value that's to o far away from the real result and since fraction already equals zero at this stage, the value can't be improved. how can I correct it? – scifie Mar 22 '15 at 22:28
  • 1
    The maclaurin series in the code is designed for pi=3.14. If you have angles in degrees, you need to convert to radians first. The starting `fraction` should always be between -1.57 and +1.57. – user3386109 Mar 22 '15 at 22:33
  • 1
    @scifie: The argument reduction for trig functions of large argument is an advanced topic. For arguments up to medium size, you can use what is know as a Cody-Waite method (W. J. CODY, JR. and W. WAITE. Software Manual for the Elementary Functions. Prentice-Hall, Englewood Cliffs, NJ, 1980), and for really large arguments there is the Payne-Hanek method (M. Payne and R. Hanek. Radian reduction for trigonometric functions. SIGNUM Newsletter, 18:19–24, 1983) – njuffa Mar 22 '15 at 22:45
  • I have done it - converted x to radX = (x*PI)/180 but after using radX in the same algorithm instead of x and afterwards convering the result back to degrees via (result*180)/PI the results were different than expected - larger than 1 - when I ran this algorithm on x=1.5 (before conversion to radians) and N=1 it returned 1.499829 unlike the original version (using degrees) which gave the correct answer - which is 0.937500. – scifie Mar 22 '15 at 23:12
  • 2
    There is no converting back to degrees. The function takes an `x` in radians and returns a result between -1 and 1. If you have an angle in degrees, you need to convert it to radians before calling the function (your comment has the right formula), but the result of the function *is* the answer, no additional conversions necessary. – user3386109 Mar 22 '15 at 23:29
  • Ok, my bad - the x is already in radians. so no conversions are necessary. But the problem is still the same - when I use large N values to offset the large values of x (in radians) such as x=44 eventually the factorial grows so mush that fraction=0 and stops improving the result and bringing it closer to the real value and because the original value x was so large to begin with that the result is stack on a large value that's too far away from the real result and since fraction already equals zero at this stage, the value can't be improved. how can I correct it? – scifie Mar 23 '15 at 22:19

2 Answers2

2

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);
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • You could even, instead of converting to radians, divide by 180 (an operation that will be exact in some cases and always is the nearest approximation of what you want to do mathematically, unlike `x = x*PI/180;`) and apply `sinpi`. – Pascal Cuoq Mar 23 '15 at 06:28
  • @Pascal Cuoq What systems define `sinpi()`? Have not found it as a standard function. – chux - Reinstate Monica Mar 23 '15 at 14:20
  • 2
    OS X has it as a standard function. CUDA and CRlibm also provide it. PS: Ok, found the man page for OS X: `man __sinpi` – Pascal Cuoq Mar 23 '15 at 14:49
  • I guess that those who say "it is exact" assume that degrees are integer values. But what about 6.333333333 degrees (~ 6 degrees 20 minutes) etc.? – Rudy Velthuis Mar 23 '15 at 16:16
  • @Rudy Velthuis OP's code passes a value `double x` to `calcMaclaurinPolynom()`. `fmod(x, 360)` is expected to provide an _exact_ mathematical result. [Is fmod() exact when y is an integer?](http://stackoverflow.com/questions/20928253/is-fmod-exact-when-y-is-an-integer). This code does not assume `x` has an integer value. There is no loss of precision using `fmod()`. Operations like `* / + -` may incur precision loss. – chux - Reinstate Monica Mar 23 '15 at 16:22
  • @Rudy Velthuis Further: `double x = 6.333333333;`, assuming binary64, takes on the value `6.333333332999999676...625`. Code `calcMaclaurinPolynom(x)` does not impart any value change in the reduction steps where I asserted "This reduction is exact". `x` retains the value `6.333333332999999676...625` – chux - Reinstate Monica Mar 23 '15 at 17:42
  • In my recollection, Solaris was the first platform to offer a `sinpi()` function: http://docs.oracle.com/cd/E37069_01/html/E54439/sinpi-3m.html. OpenCL has `sinpi()`: https://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/mathFunctions.html. I believe Apple added it to their libraries recently but cannot find a handy reference. – njuffa Mar 23 '15 at 19:15
  • I was wrong earlier - the x being passed to the function is already in radians so no conversions are necessary. But the problem is still the same - when I use large N values to offset the large values of x (in radians) such as x=44 eventually the factorial grows so mush that fraction=0 and stops improving the result and bringing it closer to the real value and because the original value x was so large to begin with that the result is stack on a large value that's too far away from the real result and since fraction already equals zero at this stage, the value can't be improved. – scifie Mar 23 '15 at 22:21
  • How can that be corrected - how can I manipulate x which I now know is in radians so I can somehow reduce the size of values like x=44 to a smaller number. – scifie Mar 23 '15 at 22:22
  • 1
    @scifie use various functions like `x = fmod(x, 2*PI)` and `if()` like in this answer but now with radians to get `x` into the range -pi/2 to pi/2. _Then_ apply the series. If you want an authoritative method to do this with the best possible results: [ARGUMENT REDUCTION FOR HUGE ARGUMENTS: Good to the Last Bit](www.csee.umbc.edu/~phatak/645/supl/Ng-ArgReduction.pdf‎) – chux - Reinstate Monica Mar 23 '15 at 22:36
  • @scifie: In math libraries a commonly used approach to argument reduction for trigonometric functions is to subtract (or add) a multiple of π/2, such that the reduced argument is in [- π/4, + π/4]. One then applies a minimax polynomial approximation, rather than using a Maclaurin series. It is easy enough to find the correct multiple n by the computation n = `rint` (x * 2/ π). The hard part is to accurately perform the computation of reduced_x = x +/- n * π/2. This is where the Cody-Waite and Payne-Hanek methods come in that I mentioned in an earlier comment. – njuffa Mar 24 '15 at 00:56
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
Community
  • 1
  • 1
Lutz Lehmann
  • 25,219
  • 2
  • 22
  • 51
  • But I need to use the maclaurin series for sin, so how do I incorporate trigonometric functions into this formula which contains only x and factorials? – scifie Mar 22 '15 at 23:58
  • This method has two levels, the inner level where the power series is used to compute the trig. values, and an outer level that first reduces the argument and then processes the trig. values for the reduced argument into the trig. values for the original argument. – Lutz Lehmann Mar 23 '15 at 00:13