R:使用`(1 + 1 / n)来近似`e = exp(1)`^ n`会在`n`大时产生荒谬的结果

时间:2018-08-01 03:01:01

标签: r exponential

因此,我只是在手动计算R中e的值,然后发现有些东西令我感到不安。

使用R的e命令获取exp()的值...

exp(1)
#[1] 2.718282

现在,我将尝试使用x = 10000

进行手动计算
x <- 10000
y <- (1 + (1 / x)) ^ x
y
#[1] 2.718146

不太清楚,但我们将尝试使用x = 100000

x <- 100000
y <- (1 + (1 / x)) ^ x
y
#[1] 2.718268

温暖,但还是有点...

x <- 1000000
y <- (1 + (1 / x)) ^ x
y
#[1] 2.71828

现在,让我们尝试一个巨大的

x <- 5000000000000000
y <- (1 + (1 / x)) ^ x
y
#[1] 3.035035

嗯,那是不对的。这里发生了什么?我是否溢出数据类型,而需要使用某个包?如果是这样,当您溢出数据类型时是否没有警告?

2 个答案:

答案 0 :(得分:7)

您的机器精度存在问题。 (1 / x) < 2.22e-16刚好为1 + (1 / x)。1在有限精度数值计算中,数学极限被打破。问题中的最后一个x已经是5e+15,非常接近这个边缘。尝试x <- x * 10,您的y将是1

这既不是“上溢”也不是“下溢”,因为表示1e-308之类的数字并不困难。这是浮点算术期间丢失有效数字的问题。当您执行1 + (1 / x)时,x越大,将(1 / x)部分添加到1时可以保留的有效数字越少,最终(1 / x)就丢失了一词。

               ## 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|

任何数值分析书都会告诉您以下内容。

  • 避免添加大量和少量。在浮点加法a + b = a * (1 + b / a)中,如果b / a < 2.22e-16,则有a + b = a。这意味着当将多个正数相加时,将它们从最小到最大累加会更稳定。
  • 避免从另一个相同大小的数字中减去一个数字,否则您可能会得到cancellation error。该网页上有一个使用二次公式的经典示例。

还建议您阅读Approximation to constant "pi" does not get any better after 50 iterations,这是在您提问几天后问到的一个问题。使用series逼近无理数在数值上是稳定的,因为您不会在问题中看到荒谬的行为。但是有限数量的有效有效数字会带来一个不同的问题:数值收敛,即,您只能将目标值近似化为一定数量的有效数字。 MichaelChirico's answer使用泰勒级数将在19个项后收敛,因为1 / factorial(19)在加到1时已经在数字上为0。

浮点数之间的乘法/除法不会导致有效数字出现问题;它们可能会导致“上溢”或“下溢”。但是,鉴于可表示的浮点值范围很广(1e-308〜1e + 307),“上溢”和“下溢”应该很少。真正的困难在于加/减,其中很容易丢失有效数字。有关矩阵计算的示例,请参见Can I stably invert a Vandermonde matrix with many small values in R?。达到更高的精度并非不可能,但工作可能会涉及更多。例如,矩阵示例的OP最终使用GMP (GNU Multiple Precision Arithmetic Library)和相关的R包继续进行:How to put Rmpfr values into a function in R?

答案 1 :(得分:3)

您还可以尝试泰勒级数逼近exp(1)

e^x = \sum_{k = 0}{\infty} x^k / k!

因此,我们可以通过截断该和来逼近e = e^1;在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"