0

original outdated code: Write an algorithm that compute the Euler's number until

My professor from Algorithms course gave me the following homework:

Write a C/C++ program that calculates the value of the Euler's number (e) with a given accuracy of eps > 0. Hint: The number e = 1 + 1/1! +1/2! + ... + 1 / n! + ... = 2.7172 ... can be calculated as the sum of elements of the sequence x_0, x_1, x_2, ..., where x_0 = 1, x_1 = 1+ 1/1 !, x_2 = 1 + 1/1! +1/2 !, ..., the summation continues as long as the condition |x_(i+1) - x_i| >= eps is valid.

As he further explained, eps is the precision of the algorithm. For example, the precision could be 1/100 |x_(i + 1) - x_i| = absolute value of ( x_(i+1) - x_i )

Currently, my program looks in the following way:

#include<iostream>
#include<cstdlib>
#include<math.h>

// Euler's number

using namespace std;

double factorial(double n)
{
    double result = 1;
    for(double i = 1; i <= n; i++)
    {
        result = result*i;
    }
    return result;
}

int main()
{
    long double euler = 2;

    long double counter = 2;
    long double epsilon = 1.0/1000;
    long double moduloDifference;

    do
    {
        euler+=  1 / factorial(counter);
        counter++;
        moduloDifference = (euler + 1 / factorial(counter+1) - euler);
    } while(moduloDifference >= epsilon);
    printf("%.35Lf ", euler );
    return 0;
}

Issues:

  1. It seems my epsilon value does not work properly. It is supposed to control the precision. For example, when I wish precision of 5 digits, I initialize it to 1.0/10000, and it outputs 3 digits before they get truncated after 8 (.7180).
  2. When I use long double data type, and epsilon = 1/10000, my epsilon gets the value 0, and my program runs infinitely. Yet, if change the data type from long double to double, it works. Why epsilon becomes 0 when using long double data type?
  3. How can I optimize the algorithm of finding Euler's number? I know, I can rid off the function and calculate the Euler's value on the fly, but after each attempt to do that, I receive other errors.
max
  • 166
  • 13
  • You have to take the absolute value of the difference before comparing it to your epsilon – king_nak Jan 13 '21 at 09:08
  • 1
    1. why factorial? You know you are computing `1, 1*2, 1*2*3, 1*2*3*4 ...`. so why not use last result and just multiply it by `i` ? 2. As the Jerry Coffin answer suggest adding big and small values will not work ... he suggest reversing order but that is not possible without knowing `n` ahead or having "limitless" memory. Instead you need to split the sub-result into big and small magnitudes something like I did in [here look for `[edit1] integration precision`](https://stackoverflow.com/a/28020934/2521214) – Spektre Jan 13 '21 at 09:11
  • Also in case you are not bound to the equation there is a way better suited for computation of `e` on binary computers using `e = (1+1/x)^x` see [Euler number with tasks](https://stackoverflow.com/a/38038334/2521214)... the precision is just a function of `bits` which can be compute from `log(precision)/log(2)` ... however this will not require diference testing anymore so if this is an assignment its possible this would not be considered as correct homework ... – Spektre Jan 13 '21 at 09:15
  • "for(double i = 1; i <= n; i++)" As a general recommendation not only for this particular issue here: Lines like that are almost always wrong/at least error-prone! Try to always iterate with integral types! – Secundi Jan 13 '21 at 09:18
  • @Secundi that is not true and in this case its better to have it as is (as its faster than converting in each iteration) ... floating point can represent integers exactly (until mantissa limits are crossed) so they are not wrong ... – Spektre Jan 13 '21 at 09:21
  • 3. I am missing some sanity check/limit for the factorial ... you know they [grow rapidly](https://stackoverflow.com/a/18333853/2521214) so you should limit the `n` to avoid overflow which will lead to infinite loop – Spektre Jan 13 '21 at 09:25
  • @Spektre, ok I admit, that the situation here is a bit special since the doubles are semantical integer replacements. But then, at least n should be provided as an integral type within the method's signature to avoid possible misusage. – Secundi Jan 13 '21 at 09:36
  • 1
    @Secundi yep I would add `.0` to all the floating literals just to be sure no casting between integer/float nor rounding occurs ... I am a bit paranoid about this stuff as I am dealing with compilers (time to time) that could `1/2.5` truncate to integer arithmetics and then cast back to float making stuff imprecise and slow ... – Spektre Jan 13 '21 at 09:40
  • just a silly example let have precision 10000 (5 digits after decimal dot) so `ceil(log2(100000)) = 17` and `x=2^17=131072` so `e = (1 + 1/x)^x = 1.00000762939453125^131072 = 2.7182714591093061987333633780002` truncating to 5 digits after decimal dot: `e = 2.71827` against `ref e = 2.7182818284590452353602874713527` – Spektre Jan 13 '21 at 09:46
  • 1
    @max, please update your question in general here since your almost duplicated question received some helpful answers already there, especially in terms of optimization. Maybe the original question should be marked as duplicated. – Secundi Jan 13 '21 at 10:54
  • @Spektre, "as its faster than converting in each iteration" maybe I was not precise enough: result * static_cast(i); for instance needn't to be slower a priori, not really for modern compilers and architectures. Made some tests with heavy optimization and the benchmarks are almost equal (for non-inf result case). – Secundi Jan 13 '21 at 11:19
  • @Secundi yes on PC its usually OK these days ... but then you go for MCU and or DSP and suddenly all sorts of weirdness happen ... its better to write the code in safe way in the first place (but that is just my opinion) – Spektre Jan 13 '21 at 11:23
  • @Spektre, yes but under this premisse, assuming the IEEE 754 is always the used floating point representation could be questioned further on (as does apply on speculation about the ++operators actual hardware level realization)...Becomes a bit off topic now, but thanks for the hints! Gave me motivation to re-read floating point arithmetics stuff the next time. – Secundi Jan 13 '21 at 11:39
  • Good that you successfully managed to ask your question again. However, now I understand why it was tagged duplicate. I rolled back the previous question, because you asked a new question about the output only showing 5 decimals. But with this new question, you are asking different things altogether... – JHBonarius Jan 13 '21 at 11:51

2 Answers2

3

One problem with computing Euler's constant this way is pretty simple: you're starting with some fairly large numbers, but since the denominator in each term is N!, the amount added by each successive term shrinks very quickly. Using naive summation, you quickly reach a point where the value you're adding is small enough that it no longer affects the sum.

In the specific case of Euler's constant, since the numbers constantly decrease, one way we can deal with them quite a bit better is to compute and store all the terms, then add them up in reverse order.

Another possibility that's more general is to use Kahan's summation algorithm instead. This keeps track of a running error while it's doing the summation, and takes the current error into account as it's adding each successive term.

For example, I've rewritten your code to use Kahan summation to compute to (approximately) the limit of precision of a typical (80-bit) long double:

#include<iostream>
#include<cstdlib>
#include<math.h>
#include <vector>
#include <iomanip>
#include <limits>

// Euler's number

using namespace std;

long double factorial(long double n)
{
    long double result = 1.0L;
    for(int i = 1; i <= n; i++)
    {
        result = result*i;
    }
    return result;
}

template <class InIt>
typename std::iterator_traits<InIt>::value_type accumulate(InIt begin, InIt end) {
    typedef typename std::iterator_traits<InIt>::value_type real;
    real sum = real();
    real running_error = real();

    for ( ; begin != end; ++begin) {
        real difference = *begin - running_error;
        real temp = sum + difference;
        running_error = (temp - sum) - difference;
        sum = temp;
    }
    return sum;
}

int main()
{  
  std::vector<long double> terms;
  long double epsilon = 1e-19;

  long double i = 0;
  double term;
 
  for (int i=0; (term=1.0L/factorial(i)) >= epsilon; i++)
    terms.push_back(term);

  int width = std::numeric_limits<long double>::digits10;

  std::cout << std::setw(width) << std::setprecision(width) << accumulate(terms.begin(), terms.end()) << "\n";
}

Result: 2.71828182845904522

In fairness, I should actually add that I haven't checked what happens with your code using naive summation--it's possible the problem you're seeing is from some other source. On the other hand, this does fit fairly well with a type of situation where Kahan summation stands at least a reasonable chance of improving results.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2
#include<iostream>
#include<cmath>
#include<iomanip>

#define EPSILON  1.0/10000000
#define AMOUNT  6

using namespace std;

int main() {
    
    long double e = 2.0, e0;
    long double factorial = 1;

    int counter = 2;
    long double moduloDifference;

    do {
        e0 = e;
        factorial *= counter++;
        e += 1.0 / factorial;

        moduloDifference = fabs(e - e0);
    } while (moduloDifference >= EPSILON);

    cout << "Wynik:" << endl;
    cout << setprecision(AMOUNT) << e << endl;
    return 0;
}

This an optimized version that does not have a separate function to calculate the factorial.

Issue 1: I am still not sure how EPSILON manages the precision.

Issue 2: I do not understand the real difference between long double and double. Regarding my code, why long double requires a decimal point (1.0/someNumber), and double doesn't (1/someNumber)

max
  • 166
  • 13
  • 1
    By default `1.0` is `double`. If you want a `float` use `1.0f` or `1.0F` and a `long double` is `1.0l` or `1.0L`. Thus `EPSILON` will be `double`. By the way, you should switch from using C-style `#define` to `constexpr` statements. **Note** [`fabs` uses `double`](https://en.cppreference.com/w/c/numeric/math/fabs). – JHBonarius Jan 13 '21 at 20:12
  • Issue 2 should not be an issue. If `1/factorial` doesn't work, it's a compiler bug or so. What compiler are you using? – JHBonarius Jan 13 '21 at 20:13
  • @JHBonarius gcc compiler – max Jan 13 '21 at 20:21