我可以修复此递归函数,以使调用不深入吗?

时间:2019-08-04 16:09:43

标签: r recursion stack-overflow

我正在上R课,并被要求实现牛顿的平方根方法。我之前已经做过,但是在使用尾部递归的函数式语言中,堆栈没有填充,因为数学是通过每个递归调用完成的,而不是在回调上完成的。

我已经实现了该功能。但是当我将函数应用于非常大的数字时,出现错误:“错误:C堆栈使用率15924912太接近限制了”。我想知道我的功能是否可以修改以解决此问题。

my_sqr <- function(number, sqrt_guess = 1) {
  if (abs((number/sqrt_guess) - sqrt_guess) < .001) sqrt_guess
  else my_sqr(number, improve_guess(number,sqrt_guess))
}

improve_guess <- function(number, guess) {
  return ((guess + (number/guess)) / 2)
}

# test your script on few examples here, example
# Note I will use the results in check1, check2, check3 to grade your sqrt function
# my_test1 <- my_sqr(16)
# my_test2 <- my_sqr(25)
# my_test3 <- my_sqr(400)
# my_test4 <-my_sqr(5000000000000000)
check1 <- my_sqr(2)
check2 <- my_sqr(1e-60)
check3 <- my_sqr(1e+60)

该函数可用于除最后一次调用“ my_sqr(1e + 60)”之外的所有测试。这是我得到错误的地方。

2 个答案:

答案 0 :(得分:3)

该错误使您无法进入永无止境的循环。您可以改用此功能,但是使用1e + 56或更高版本可能永远不会结束...

#here you can see those limits
Cstack_info()

#here is the code

library(rbenchmark)  

new_my_sqr <- function(number, sqrt_guess = 1) {
    while (abs((number/sqrt_guess) - sqrt_guess) > .001) {
        sqrt_guess <- ((sqrt_guess + (number/sqrt_guess)) / 2)
    }
    return (sqrt_guess)
}

#You can compare execution time with something like this...

benchmark("See the change in time from 1e+55..." = {check3x1 <- new_my_sqr(1e+55)},
          "...to 1e+56" = {check3x2 <- new_my_sqr(1e+56)},
          replications = 2,
          columns = c("test", "replications", "elapsed")
)

答案 1 :(得分:1)

紧跟@CésarArquero的回答,就“避免递归”部分而言,这是好的,但实际上并没有解决问题的根源-浮点不精确。这些问题可能会影响递归和非递归实现:您要么需要(1)重新构造问题以避免不精确; (2)设置最大迭代次数以避免无限循环结果;或(3)使用高精度算术(例如library("Rmpfr")-尽管通常是不得已而为之)。

如下所示,对于算法进入无限循环的大值,它需要进行<500次迭代,因此在1447次迭代时崩溃(在上述@RuiBarradas的注释中提到)可能来自无限循环。

这是@CésarArquero函数的增强版本,该函数设置最大迭代次数并输出有关进度的信息:

new_my_sqr <- function(number, sqrt_guess = 1, maxit = 10000, tol = 0.001) {
    it <- 0
    dval <- abs((number/sqrt_guess) - sqrt_guess)
    while (it < maxit &&  dval > tol ) {
        sqrt_guess <- (sqrt_guess + number/sqrt_guess) / 2
        dval <- abs((number/sqrt_guess) - sqrt_guess)
        it <- it + 1
        cat(it, sqrt_guess, dval, "\n")
    }
    return (sqrt_guess)
 }

对于100,一切看起来都很明智-答案与猜测之间的距离会平滑地收敛到公差。

new_my_sqr(100)
## 1 50.5 48.5198 
## 2 26.2401 22.42914 
## 3 15.02553 8.370191 
## 4 10.84043 1.615712 
## 5 10.03258 0.06505123 
## 6 10.00005 0.000105791 

如果我们使用更大的参数(尽管我们仍然可以获得正确的答案),那么事情看起来会更成问题:

new_my_sqr(1e30)
## ...
## 51 1.022386e+15 4.428175e+13 
## 52 1.000245e+15 490098151072 
## 53 1e+15 60049048 
## 54 1e+15 1 
## 55 1e+15 0 

类似地...

new_my_sqr(1e54)
## 90 1.183618e+27 3.387511e+26 
## 91 1.014243e+27 2.828522e+25 
## 92 1.0001e+27 1.999934e+23 
## 93 1e+27 9.999345e+18 
## 94 1e+27 0 

在1e54和1e56之间的某个地方,我们切换到一个无限循环(或者,如果我没有施加最大的迭代次数,那将是无限循环)。

new_my_sqr(1e56)
## 9997 1e+28 2.199023e+12 
## 9998 1e+28 2.199023e+12 
## 9999 1e+28 2.199023e+12 
## 10000 1e+28 2.199023e+12 

我还没有花时间确切地计算出数值下溢问题的工作原理:一般的想法是,如果我们尝试对大小相差很大的项进行加/减,就会产生下溢。特别是sqrt_guess + number/sqrt_guess1 + number/(sqrt_guess^2)成正比,因此,如果我们最终遇到number/(sqrt_guess^2)非常小的点,我们将遭受灾难性的精度损失。

我做了一些数值实验;我们不会总是陷入困境。

enter image description here