tags:

views:

1025

answers:

5

I'm trying to get the exact equivalent (not functional) of this vb.net code in F#:

Function FastPow(ByVal num As Double, ByVal exp As Integer) As Double
   Dim res As Double = 1
   If exp < 1 Then
      If exp = 0 Then Return res
      exp = -exp
      num = 1 / num
   End If
   Do While exp > 1
      If exp Mod 2 = 1 Then 
         res = res * num
      num = num * num
      exp = exp >> 1
   Loop
   Return res * num
End Function

I wrote this:

let FastPow num exp =
   let mutable ex = exp
   let mutable res = 1
   let mutable n = num
   if ex < 1 then
      if ex = 0 then res
      ex <- -ex
      n <- 1 / n
   while ex > 1 do
      if (ex % 2 = 1) then 
         res <- res * n
      n <- n * n
      exp >>> 1
   res * n

but in the line "if ex = 0 then res" at res I got an error:
"This expression has type int but is here used with type unit". I cannot understand why it gives me that error.
Edit: i actually got a warning as well:
"This expression should have type 'unit', but has type 'int'."
at "if (ex % 2 = 1) then"

A: 

It means that after then there should be some expression, but you have integer value. You cannot jump out from the middle of the function.

Edit

"If" didn't work because of

ex >>> 1

should be

ex <- ex >>> 1

Here's code that works:

let FastPow num exp =
    let calcExp num exp = 
        let mutable res = 1.0
        let mutable n   = num
        let mutable ex  = exp
        while ex > 1 do
            if ((ex % 2) = 1) then  
                res <- res * n
            n <- n * n
            ex <- ex >>> 1
        res * n

    match exp with
    | ex when ex = 0 -> 1.0
    | ex when ex < 0 -> calcExp (1.0/num) -exp
    | _ -> calcExp num exp

I just take out calculation as separate function, and at the end there is checking for arguments

zendar
+2  A: 

The problem is for an if statment to resolve to a value rather than unit, you need both the "then" part and the "else" part, both of which resolve to the same type.

For example:

let a = if true then 1;;

Will generate the same error - expression has type int but used with type unit.

However:

let a = if true then 1 else 0;;

Will evaluate to int without an error.

Chris Ballard
+1  A: 

This is about as close as you can get, as others have already said you can't jump out of the middle of a functional and there's one place were you don't update a variable (at the bottom of the while).

let FastPow num exp =
   let mutable exp = exp
   let mutable res = 1
   let mutable n = num
   match exp with
   | O -> n <- num
   | _ when exp < 1 ->
      exp <- -exp
      n <- 1 / n
   | _ ->
       while exp > 1 do
          if (exp % 2 = 1) then 
             res <- res * n
          n <- n * n
          exp <- exp >>> 1
   res * n

I could be more beautiful if it was written more functionally.

Robert
+7  A: 

In F#, a function's return value is the last expression evaluated in the function. So, lets focus on the following:

   if ex < 1 then
      if ex = 0 then res    (* <--- this is not an early return *)
      ex <- -ex             (* <--- F# evaluates this code after the *)
      n <- 1 / n            (*      if statement *)

Additionally, if statements have return values, which also happens to be the last value executed in the if statement. If an if statement isn't the return value of a function, it should have the return type unit. Notice that variable assignment has a return type of unit.

We need to rewrite your code to accomodate your early return, so we can do this:

let FastPow2 num exp =
    if exp = 0 then 1
    else
        let mutable ex = exp
        let mutable res = 1
        let mutable n = num
        if ex < 1 then
            ex <- -ex
            n <- 1 / n
        while ex > 1 do
            if (ex % 2 = 1) then  (* still have a bug here *)
                res <- res * n
            n <- n * n
            exp >>> 1  (* <--- this is not a variable assignment *)
        res * n

We still have a bug, although I think F# is reporting the error in the wrong place. The expression exp >>> 1 returns an int, it does not assign any variables, so its not equivalent to your original C# code. I think you meant to use the ex variable instead. We can fix your code as follows:

let FastPow2 num exp =
    if exp = 0 then 1
    else
        let mutable ex = exp
        let mutable res = 1
        let mutable n = num
        if ex < 1 then
            ex <- -ex
            n <- 1 / n
        while ex > 1 do
            if (ex % 2 = 1) then 
                res <- res * n
            n <- n * n
            ex <- ex >>> 1
        res * n

Now your function is fixed, but its really really ugly. Lets convert it to more idiomatic F#. You can replace the if statement with pattern matching, and replace the while loop with recursion:

let FastPow2 num exp =
    match exp with 
    | 0 -> 1
    | _ ->
        let rec loop ex res n =
            if ex > 1 then
                let newRes = if ex % 2 = 1 then res * n else res
                loop (ex >>> 1) newRes (n * n)
            else res * n

        let ex, n = if exp < 1 then (-exp, 1 / num) else (exp, num)
        loop ex 1 n

Much better! Theres still some more room to beautify this function, but you get the idea :)

Juliet
A: 

Thanks for the answers. This is the current non-functional version.

let FastPow num exp =
   let mutable ex = exp
   let mutable res = 1.0
   let mutable n = num
   if ex = 0 then 1.0
   else 
      if ex < 1 then
         ex <- -ex
         n <- 1.0 / n
      while ex > 1 do
         if (ex % 2 = 1) then res <- res * n
         n <- n * n
         ex <- ex >>> 1
      res * n

Now that I have a working version I will try to make it more functional but that's outside the scope of this question. EDIT: I got better results that I expected so I will post the recursive version optimized for speed (slightly faster than the iterative version and about 10% faster than the C# iterative version (!!!) in my computer):

let rec loop res num exp =
   if exp = 0 then res
   elif (exp % 2) = 1 then loop (res * num) (num * num) (exp / 2)
   else loop res (num * num) (exp / 2)

let FP num exp =
   let n = if exp < 0 then 1.0 / num else num
   loop 1.0 n (Math.Abs(exp))
ggf31416