Reputation:
I encountered a stack overflow issue when implementing a square root approximation algorithm by Heron of Alexandria, given as follows:
We start with an initial (poor) approximate answer that the square root is 1.0 and then continue improving the guess until we're within delta of the real answer. The improvement is achieved by averaging the current guess with x/guess. The answer is accurate to within delta = 0.0001.
My attempt at an implementation was as follows:
let squareRoot (x : float) : float =
let rec aux input guess =
if abs_float(guess**2. -. input) < 0.0001 then guess
else aux input (guess +. input/.guess)/.2. in
aux x 1.;;
However, this raises a # Stack overflow during evaluation (looping recursion?).
error in the OCaml REPL. I tried implementing an identical algorithm in Python as follows:
def squareRoot(x):
def aux (s, guess):
if abs(pow(guess,2) - s) < 0.0001:
return guess
else:
return aux (s, (guess + s/guess)/2)
return aux (x, 1)
...which ran just fine. So I played around with the OCaml code, and changed my original attempt to:
let squareRoot (x : float) : float =
let improve i g = (g +. i/.g)/.2. in
let rec aux input guess =
if abs_float(guess ** 2. -. input) < 0.0001 then guess
else aux input (improve input guess) in
aux x 1.;;
All I changed was wrapping the improvement portion of the algorithm in a separate function, but now the code runs successfully, without any stack overflow error!
I'd appreciate if someone could explain why this is so, and the mechanism behind the OCaml REPL/compiler possibly not recognizing a terminating condition in the recursive call in the my first iteration of code, etc.
Upvotes: 3
Views: 576
Reputation: 1
aux input (guess +. input/.guess)/.2.
(the application of aux
happens before the division by 2.
...)
is parsed as
(aux input (guess +. input/.guess))/.2
You really want
aux input ((guess +. input/.guess)/.2.)
or even (read about A-normal forms)
let newguess = (guess +. input/.guess)/.2.
in
aux input newguess
(which could be more readable, some people use names like guess'
)
BTW, some people would code
let guess = aux input ((guess +. input/.guess)/.2.)
in aux input guess
(there is no recursion, but lexical scoping)
but I don't like coding like that (reusing the same name guess
)
As a rule of thumb, don't be shy in using parenthesis (or begin
... end
which is the same) and intermediate let
bindings. Both makes your code more readable.
Upvotes: 5