我正在上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)”之外的所有测试。这是我得到错误的地方。
答案 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_guess
与1 + number/(sqrt_guess^2)
成正比,因此,如果我们最终遇到number/(sqrt_guess^2)
非常小的点,我们将遭受灾难性的精度损失。
我做了一些数值实验;我们不会总是陷入困境。