Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a more elegant Go implementation of Newton's method?

I'm doing the Go tutorials and am wondering whether there is a more elegant way to compute a square root using Newton's method on Exercise: Loops and Functions than this:

func Sqrt(x float64) float64 {
    count := 0
    var old_z, z float64 = 0, 1
    for ; math.Abs(z-old_z) > .001; count++ {
        old_z, z = z, z - (z*z - x) / 2*z
    }
    fmt.Printf("Ran %v iterations\n", count)
    return z
}

(Part of the specification is to provide the number of iterations.) Here is the full program, including package statement, imports, and main.

like image 231
Ellen Spertus Avatar asked Dec 24 '22 20:12

Ellen Spertus


1 Answers

First, you algorithm is not correct. The formula is:

enter image description here

You modelled this with:

z - (z*z - x) / 2*z

But it should be:

z - (z*z - x)/2/z

Or

z - (z*z - x)/(2*z)

(Your incorrect formula had to run like half a million iterations even to just get as close as 0.001! The correct formula uses like 4 iterations to get as close as 1e-6 in case of x = 2.)

Next, initial value of z=1 is not the best for a random number (it might work well for a small number like 2). You can kick off with z = x / 2 which is a very simple initial value and takes you closer to the result with fewer steps.

Further options which do not necessarily make it more readable or elegant, it's subjective:

You can name the result to z so the return statement can be "bare". Also you can create a loop variable to count the iterations if you move the current "exit" condition into the loop which if met you print the iterations count and can simply return. You can also move the calculation to the initialization part of the if:

func Sqrt(x float64) (z float64) {
    z = x / 2
    for i, old := 1, 0.0; ; i++ {
        if old, z = z, z-(z*z-x)/2/z; math.Abs(old-z) < 1e-5 {
            fmt.Printf("Ran %v iterations\n", i)
            return
        }
    }
}

You can also move the z = x / 2 to the initialization part of the for but then you can't have named result (else a local variant of z would be created which would shadow the named return value):

func Sqrt(x float64) float64 {
    for i, z, old := 1, x/2, 0.0; ; i++ {
        if old, z = z, z-(z*z-x)/2/z; math.Abs(old-z) < 1e-5 {
            fmt.Printf("Ran %v iterations\n", i)
            return z
        }
    }
}

Note: I started my iteration counter with 1 because the "exit" condition in my case is inside the for and is not the condition of for.

like image 179
icza Avatar answered Dec 28 '22 16:12

icza