0

I'm trying to approximate hyperbolic sine. I need to do it without using math.h library functions.

[don't provide me full solutions, just some hint, because I need to figure it out by myself]

here's what I did:

given the hyperbolic sine taylor series, I need to compute the factorial of (2*n + 1). to do it, I need to do only this step:

fact *= (2*i +1); // inside a for-loop. 

I need to compute the power x^(2*n +1), and I did this way:

double power(double x, unsigned int y) {
    double result = 1;
    for (unsigned int i = 0; i < y; i++) {
        result *= x;
    }
    return result;
}

now, I have every pieces, the taylor series is implemented as follows:

#include <stdio.h> 

double power(double x, unsigned int y) {
    double result = 1;
    for (unsigned int i = 0; i < y; i++) {
        result *= x;
    }
    return result;
}

double hyp_sin(double x) {
    double result = 0;
    double fact = 1;
    double pow = 0;
    for (unsigned int i = 0; i != 21; i++) {
        fact *= (2 * i + 1);
        pow = power(x, 2 * i + 1);
        result += ((1 / fact) * pow);
    }
    return result;
}

int main(void) {
    double result = hyp_sin(89.9878);
    printf("%lf", result);
    return 0;
}

The result is completely wrong, it should have been 6.028024141598018316924203992363e+38 (with 21 iterations) result is the following

wohlstad
  • 12,661
  • 10
  • 26
  • 39
  • 1
    Your factorial is wrong: try `printf("fact at loop %u: %f\n", i, fact);` – pmg Feb 13 '22 at 08:19
  • 2
    you are doing [double factorial](https://en.wikipedia.org/wiki/Double_factorial) instead of regular one – Amir Feb 13 '22 at 08:36
  • I didn't understand why this is double factorial and not the regular one. Meaning, fact stores the previous value, and the rvalue is the next value. but the rvalue is not doubled, it is exactly the factorial part of the hyperbolic sine Taylor series. – Gabriel Burzacchini Feb 13 '22 at 09:01
  • 1
    @GabrielBurzacchini If you just use the loop to compute and print the `fact` variable, you'll see that the code is computing `1` and then `1*3` and `1*3*5` etc, when you should be computing `1` and `1*2*3` and `1*2*3*4*5`. – user3386109 Feb 13 '22 at 09:05
  • 1
    Are you sure? Look again at `fact *= (2 * i + 1);`. You are computing like in the previous comment mentioned. – Ely Feb 13 '22 at 09:06
  • but this is normal, because I'm not computing the factorial of n, so it's not like n(n-1)(n-2)..., it's the factorial of 2*n+1 therefore it's like 1, 3, ... 2 isn't a solution – Gabriel Burzacchini Feb 13 '22 at 09:11
  • 2
    No Grabiel, I believe you are not seeing it right. Look at the formula for the hyperbolic sine Taylor series: `x^1/1! + x^3/3! + x^5/5! + ...` – Ely Feb 13 '22 at 09:14
  • 1
    [The Taylor series is shown here.](https://en.wikipedia.org/wiki/Hyperbolic_functions#Taylor_series_expressions). The denominator is `1!`, `3!`, `5!` and so on. – user3386109 Feb 13 '22 at 09:15
  • 1
    and you need to loop more than 21 times (*infinite times to be exact!*) for a better approximation (try 70 loops) – pmg Feb 13 '22 at 09:19
  • 1
    It takes 60 terms to reach 6.02e38, and 68 terms to reach 6.028e38. Going to 89 terms gets you an answer accurate to 15 significant digits, which is all you can hope for using 64-bit floating point numbers. That's a problem for your approach. To compute 89 terms, your code needs to compute `179!`, but the typical 64-bit IEEE `double` overflows at `171!`. So after you get your current code working correctly for 21 terms, you'll need to start over with a whole new approach. BTW, the correct answer for 21 terms is 4.930222466543704e+30. By "correct" I mean the expected Taylor series sum. – user3386109 Feb 13 '22 at 09:24
  • 2
    Suggestion: `double factorial(unsigned x)` because you don't want to calculate `factorial(-3.14159)` – pmg Feb 13 '22 at 11:28

1 Answers1

2

You don't really need to calculate powers and factorials separately for each element.
As explained in the comments these values get big very fast (especially the factorial).

Instead you can update the next element in the series incrementally:
You need to multiply by x*x (because each element adds 2 to the power of x), and divide by the next values required for the factorial.

This way you can get good precision using doubles, practically for as many iterations as you need (assuming the series converges).

This method is also a lot more efficient since for each iteration the calculations are minimal.

#include <stdio.h> 

/* Calculate the sum of the series: 
   x^1/1! + x^3/3! + x^5/5! + ... 
 */
double hyp_sin(double x, unsigned int num_iters)
{
    double res = 0;
    double elem = x; /* first element is: x^1/1! == x */
    for (unsigned int i = 1; i <= num_iters; ++i)
    {
        res += elem;
        /* update for next iteration: */
        elem = elem * x * x / (2*i) / (2*i+1);
    }
    return res;
}

int main(void) 
{
    unsigned int iterations = 100;
    double x = 89.9878;
    double result = hyp_sin(x, iterations);
    printf("%lf", result);
    return 0;
}

UPDATE:
Following the comment's below by @DavidC.Rankin, below you can see a version that performs as many iterations as needed until the series converges.
This is done by comparing the difference of the current element with the last one against some epsilon based on x.
You can tune CONVERGENCE_EPSILON_FACTOR according to your needs.

#include <stdio.h> 
#include <math.h>

/* Calculate the sum of the series:
   x^1/1! + x^3/3! + x^5/5! + ...
 */
double hyp_sin(double x)
{
    const double CONVERGENCE_EPSILON_FACTOR = 0.0001;

    double convergence_epsilon = fabs(x * CONVERGENCE_EPSILON_FACTOR);
    double res = 0;
    double last_elem = 0;
    double elem = x; /* first element is: x^1/1! == x */
    int i = 1;
    do
    {
        res += elem;
        /* update for next iteration: */
        last_elem = elem;
        elem = elem * x * x / (2 * i) / (2 * i + 1);
        i++;
    } while (fabs(elem - last_elem) > convergence_epsilon);

    printf("hyp_sin(%f): converged after %d iterations.\n", x, i);
    return res;
}

int main(void)
{
    double x = 89.9878;
    double result = hyp_sin(x);
    printf("result: %lf\n", result);
    return 0;
}

Output:

hyp_sin(89.987800): converged after 125 iterations.
result: 602802414159797056498630200284374106112.000000
wohlstad
  • 12,661
  • 10
  • 26
  • 39