Search code examples
cmathwhile-loopfactorial

-nan return value / e (euler) raised to a power calculation loop


I'm learning C programming and made the algorithm below to solve this problem:

Link to a pic of the problem. The code actually works, but initially the loop was with only 10 repetitions (rep <= 10), and the anwer for p = 3 was almost correct, so I changed rep <= 20. And It gave me just the exact answer from my calculator. And then I tried with a higher number, 12, and the output again was inaccurate. So I ended raising rep <= 35. If I get the loop for higher repetitions I get "-nan", and if the input for p is too high it will be the same. So just have to see the pattern to know that the problem of inaccuracy will get back as I input higher numbers which is not the case because the output will be NaN if I input a high value.

Is it possible to solve it without higher level functions? just want to know if my program is ok for the level in which I am now...

#include <stdio.h>

int main()
{
    float p; //the power for e
    float power; //the copy of p for the loop
    float e = 1; //the e number I wanna raise to the power of p
    int x = 1; //the starting number for each factorial generation
    float factorial = 1;
    int rep = 1; //the repeater for the loop

    printf( "Enter the power you want to raise: " );
    scanf( "%f", &p );

    power = p;

    while ( rep <= 35) {
        while ( x > 1) {
            factorial *= x;
            x--;
        }
        e += p / factorial;

        //printf("\nthe value of p: %f", p); (TESTER)
        //printf("\nthe value of factorial: %f", factorial); (TESTER)

        p *= power; //the new value for p
        rep++;
        factorial = 1;
        x = rep; //the new value for the next factorial to be generated

        //printf("\n%f", e); (TESTER)
    }
    printf("%.3f", e);

    return 0;
}

Sorry if I had syntax/orthography errors, I'm still learning the language.


Solution

  • Before we begin, let's write your original code as a function, with some clean-ups:

    float exp_original(float x, int rep = 35)
    {
       float sum = 1.0f;
       float power = 1.0f;
       for (int i = 1; i <= rep; i++)
       {
          float factorial = 1.0f;
          for (int j = 2; j <= i; j++)
             factorial *= j;
          power *= x;
          sum += power / factorial;
       }
       return sum;
    }
    

    There were some unnecessary variables you used which were removed, but otherwise the procedure is the same: compute the factorial from scratch.


    Let's look at the ratio between successive terms in the series:

    enter image description here

    We can thus simply multiply the current term by this expression to get the next term:

    float exp_iterative(float x, int rep = 35)
    {
       float sum = 1.0f;
       float term = 1.0f;
       for (int i = 1; i <= rep; i++)
       {
          term *= x / i;
          sum += term;
       }
       return sum;
    }
    

    Seems much simpler, but is it better? Comparison against the C-library exp function (which we assume to be maximally precise):

    x   exp (C)       exp_orig    exp_iter
    -------------------------------------------
    1   2.7182817     2.718282    2.718282
    2   7.3890562     7.3890567   7.3890567
    3   20.085537     20.085539   20.085539
    4   54.598148     54.598152   54.598152
    5   148.41316     148.41318   148.41316
    6   403.4288      403.42871   403.42877
    7   1096.6332     1096.6334   1096.6334
    8   2980.958      2980.9583   2980.9587
    9   8103.084      8103.083    8103.083
    10  22026.465     22026.467   22026.465
    11  59874.141     59874.148   59874.152
    12  162754.8      162754.77   162754.78
    13  442413.41     -nan(ind)   442413.38
    14  1202604.3     -nan(ind)   1202603.5
    15  3269017.3     -nan(ind)   3269007.3
    16  8886111       -nan(ind)   8886009
    17  24154952      -nan(ind)   24153986
    18  65659968      -nan(ind)   65652048
    19  1.784823e+08   -nan(ind)  1.7842389e+08
    20  4.8516518e+08  -nan(ind)  4.8477536e+08
    

    The two custom implementations are neck-and-neck in-terms of precision, until x = 13 where the original gives NaN. This is because the highest power term 13^35 = 9.7278604e+38 exceeds the maximum value FLT_MAX = 3.40282e+38. The accumulated term in the iterative version never reaches anywhere near the limit.