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 e
using R's exp()
command...
exp(1)
#[1] 2.718282
Now, I'll try to manually calculate it using x = 10000
x <- 10000
y <- (1 + (1 / x)) ^ x
y
#[1] 2.718146
Not quite but we'll try to get closer using x = 100000
x <- 100000
y <- (1 + (1 / x)) ^ x
y
#[1] 2.718268
Warmer but still a bit off...
x <- 1000000
y <- (1 + (1 / x)) ^ x
y
#[1] 2.71828
Now, let's try it with a huge one
x <- 5000000000000000
y <- (1 + (1 / x)) ^ x
y
#[1] 3.035035
Well, that's not right. What's going on here? Am I overflowing the data type and need to use a certain package instead? If so, are there no warnings when you overflow a data type?
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.
- Avoid adding a large number and a small number. In floating-point addition
a + b = a * (1 + b / a)
, ifb / a < 2.22e-16
, there usa + 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. - Avoid subtracting one number from another of the same magnitude, or you may get cancellation error. The web page has a classic example of using the quadratic formula.
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"
来源:https://stackoverflow.com/questions/51624794/r-approximating-e-exp1-using-1-1-n-n-gives-absurd-result-when