So, I was just playing around with manually calculating the value of e
in R and I noticed something that was a bit disturbing to me.
The value of
You've got a problem with machine precision. As soon as (1 / x) < 2.22e-16
, 1 + (1 / x)
is just 1. Mathematical limit breaks down in finite-precision numerical computations. Your final x
in the question is already 5e+15
, very close to this brink. Try x <- x * 10
, and your y
would be 1
.
This is neither "overflow" nor "underflow" as there is no difficulty in representing a number as small as 1e-308
. It is the problem of the loss of significant digits during floating-point arithmetic. When you do 1 + (1 / x)
, the bigger x
is, the fewer significant digits in the (1 / x)
part can be preserved when you add it to 1, and eventually you lose that (1 / x)
term altogether.
## valid 16 significant digits
1 + 1.23e-01 = 1.123000000000000|
1 + 1.23e-02 = 1.012300000000000|
... ...
1 + 1.23e-15 = 1.000000000000001|
1 + 1.23e-16 = 1.000000000000000|
Any numerical analysis book would tell you the following.
a + b = a * (1 + b / a)
, if b / a < 2.22e-16
, there us a + b = a
. This implies that when adding up a number of positive numbers, it is more stable to accumulate them from the smallest to the largest.You are also advised to have a read on Approximation to constant "pi" does not get any better after 50 iterations, a question asked a few days after your question. Using a series to approximate an irrational number is numerically stable as you won't get the absurd behavior seen in your question. But the finite number of valid significant digits imposes a different problem: numerical convergence, that is, you can only approximate the target value up to a certain number of significant digits. MichaelChirico's answer using Taylor series would converge after 19 terms, since 1 / factorial(19)
is already numerically 0 when added to 1.
Multiplication / division between floating-point numbers don't cause problem on significant digits; they may cause "overflow" or "underflow". However, given the wide range of representable floating-point values (1e-308 ~ 1e+307), "overflow" and "underflow" should be rare. The real difficulty is with addition / subtraction where significant digits can be easily lost. See Can I stably invert a Vandermonde matrix with many small values in R? for an example on matrix computations. It is not impossible to get higher precision, but the work is probably more involved. For example, OP of the matrix example eventually used the GMP (GNU Multiple Precision Arithmetic Library) and associated R packages to proceed: How to put Rmpfr values into a function in R?
You might also try the Taylor series approximation to exp(1)
, namely
e^x = \sum_{k = 0}{\infty} x^k / k!
Thus we can approximate e = e^1
by truncating this sum; in R:
sprintf('%.20f', exp(1))
# [1] "2.71828182845904509080"
sprintf('%.20f', sum(1/factorial(0:10)))
# [1] "2.71828180114638451315"
sprintf('%.20f', sum(1/factorial(0:100)))
# [1] "2.71828182845904509080"