5

I am coding in ATS and am trying to make a function that finds the square root of a given integer. Provided here is code that properly meets my requirments other than it not being tail-recursion.

implement intsqrt(n) = 
if(n >= 1)
  then let
    val n4 = n / 4
    val res = 2 * intsqrt(n4) + 1
  in
    if(res * res <= n) then res else 2*intsqrt(n4)
  end
  else n

I'm not sure if others know this language well or not but it is my first week with it. I know the clear difference between regular and tail recursion, I just don't understand how this can be changed.

I don't even need the exact code to do it, I am just wondering how it is possible. In order for me to find the sqrt, I must calculate n4 = 1 / n and then multiply it by two. However, doing this goes into the recursion. What I want to be able to do is calculate a result, then pass it through to my next recursive call.

Does this mean I need to work backwards in a way? Hopefully this all makes sense but I will try to clarify if needed.

Thanks!

Will Ness
  • 70,110
  • 9
  • 98
  • 181
rmw
  • 119
  • 2
  • 10
  • Are you sure your algorithm is correct? I translated it into Scheme, it works for 4 and 16, but not for 9 and 25 unless I did an error during translation. – uselpa Jan 25 '16 at 21:47
  • @uselpa as far as I know yes. This is an assignment and the hint was "For each n >= 1, let n4 = n / 4. Then either sqrt(n) = 2*sqrt(n4) or sqrt(n) = 2*sqrt(n4) + 1." Even if it maybe doesn't work for every number, do you see any practical way to turn it into tail recursion? – rmw Jan 25 '16 at 21:50
  • @uselpa I got 3. As far as I know it works exactly how it should. This is more about how to structure my code than anything. – rmw Jan 25 '16 at 21:57
  • Regarding tail-recursion: look into so-called "accumulating parameters". – morido Jan 25 '16 at 21:59
  • I found my error (`n/4` is an integer division in ATS). I don't think you can implement this in a tail-recursive way. Are you sure it can be done? – uselpa Jan 25 '16 at 22:23
  • @uselpa Oh yeah good catch. This is what I am being asked to do but I tend to agree with you since I have no idea how. – rmw Jan 25 '16 at 22:26
  • I don't think you can make this pure tail recursion in a natural way: the value returned by the recursion has to be doubled -- and perhaps +1 after that. These intervening operations prevent pure tail recursion. – Prune Jan 25 '16 at 22:30
  • The thing is, for sqrt(25) for example it successively calls itself with 25, 6, 1, 0. For sqrt(16) it's 16, 4, 1, 0. Starting from 0 or even 1 I don't know how to determine that the next value is 4 or 6. For sqrt(9) it's 9, 2, 0 so no 1 involved. If you can find a way to create that series backwards then it's easy. – uselpa Jan 25 '16 at 22:31
  • @uselpa Yes this has been my thinking exactly. The idea is not complex but I can't find a pattern to test for in my cases as you are saying. Thank you for the help though I feel at least a little better knowing someone else has the same problem! – rmw Jan 25 '16 at 22:34
  • @uselpa Turns out the instructions were poorly written. This one does not have to be tail recursion! I guess I was right then..kinda, Sorry to waste your time. – rmw Jan 25 '16 at 22:37
  • Well I'm pretty close actually ;-) Assuming you're allowed to create a list with the series (in a tail-recursive way). But if you don't need it I'll just go to sleep. Never mind, it's an interesting question and I don't feel that you wasted my time. – uselpa Jan 25 '16 at 22:41

2 Answers2

1

In pattern-matching "pseudocode" (Haskell, where : is list-building cons and [] an empty list), your function is

isqrt n | n < 1 = n
        | res*res <= n = res
        | otherwise = 2 * isqrt(n `div` 4)
   where
        res = 2 * isqrt(n `div` 4) + 1

To turn it into a tail-recursive one we'll have to maintain the relevant data by ourselves, say, in a list. Simply iterate down towards the n < 1 case first, and then iterate back up until the simulated stack is exhausted and the final result can be produced.

The transformation is straightforward:

isqrt n = go n []
  where
    go n []     | n < 1 = n           -- initial special case
    go n (x:xs) | n < 1 = up n x xs   -- bottom reached - start going back up
    go n xs = go (n `div` 4) (n:xs)   -- no - continue still down

    up recres n (n1:ns) =             -- still some way to go up
        let res = 2*recres + 1
        in  if res*res <= n 
              then up res n1 ns       -- "return" new recursive-result
              else up (res-1) n1 ns   --   up the chain of previous "invocations"

    up recres n [] =                  -- the last leg 
        let res = 2*recres + 1
        in  if res*res <= n 
              then res                -- the final result
              else (res-1)

The code can be simplified further now.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
1

A systematic way to do this sort of thing is via CPS-transformation. What is special about the following implementation is that every byte of memory allocated during a call to intsqrt_cps is freed after the call returns. There is no GC here (unlike the above Haskell solution):

fun
intsqrt_cps
(
  n: int, k: int -<lincloptr1> int
) : int =
if
(n >= 1)
then let
  val n4 = n / 4
in
//
intsqrt_cps
( n4
, llam(res) =>
  applin(k, if square(2*res+1) <= n then 2*res+1 else 2*res)
) (* intsqrt_cps *)
//
end // end of [then]
else applin(k, 0) // end of [else]

fun intsqrt(n:int): int = intsqrt_cps(n, llam(x) => x)

The entirety of the code can be found at:

https://github.com/githwxi/ATS-Postiats/blob/master/doc/EXAMPLE/MISC/intsqrt_cps.dats

You can use valgrind to verify absence of memory leaks:

==28857== Memcheck, a memory error detector
==28857== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28857== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28857== Command: ./intsqrt_cps.exe
==28857==
intsqrt(1023) = 31
intsqrt(1024) = 32
==28857==
==28857== HEAP SUMMARY:
==28857==     in use at exit: 0 bytes in 0 blocks
==28857==   total heap usage: 14 allocs, 14 frees, 1,304 bytes allocated
==28857==
==28857== All heap blocks were freed -- no leaks are possible
==28857==
==28857== For counts of detected and suppressed errors, rerun with: -v
==28857== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Hongwei Xi
  • 895
  • 1
  • 5
  • 10