5

I'm going through tour of go and am having a problem with float64 comparison in "Exercise: Loop and functions", where you write a function to determine the square root.

From example: Computers typically compute the square root of x using a loop. Starting with some guess z, we can adjust z based on how close z² is to x, producing a better guess:

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

I wrote a function that continues to update z until the values stop changing. Only the float64 comparison never fails and this results in an infinite loop. One way to solve these types of issues is to round, but I'm not sure how to do that in golang without using the math module.

How do you round float64 numbers in golang and what is the standard way to compare floating point numbers in golang?


package main

import (
    "fmt"
)


func Sqrt(x float64) float64 {
    // Need to look into float rounding in go
    z := 1.0
    zprev := 0.01
    for z != zprev {
        zprev = z
        z -= (z*z - x) /(2*z)
        fmt.Printf("z: %g\nzprev: %g\n", z, zprev)
        fmt.Println("_________________________________________")


    }
    fmt.Println("Finished")
    return z
}


func main() {
    fmt.Println(Sqrt(2))
}

Output:

z: 1.5
zprev: 1
_________________________________________
z: 1.4166666666666667
zprev: 1.5
_________________________________________
z: 1.4142156862745099
zprev: 1.4166666666666667
_________________________________________
z: 1.4142135623746899
zprev: 1.4142156862745099
_________________________________________
z: 1.4142135623730951
zprev: 1.4142135623746899
_________________________________________
z: 1.414213562373095
zprev: 1.4142135623730951
_________________________________________
z: 1.4142135623730951
zprev: 1.414213562373095
_________________________________________
z: 1.414213562373095
zprev: 1.4142135623730951
_________________________________________
z: 1.4142135623730951
zprev: 1.414213562373095
_________________________________________
z: 1.414213562373095
zprev: 1.4142135623730951
_________________________________________

After a point z and zprev continue to alternate between 2 values that are only off by one precision point (1.414213562373095 and 1.4142135623730951) indefinitely

Lance
  • 165
  • 3
  • 13
  • 2
    Why don't you want to use the `math` package? That's where the float rounding function is. – Adrian Apr 23 '19 at 16:36
  • 2
    An alternative would be to compare the absolute difference against a given threshold... but the `Abs` function is also in the `math` package. – Adrian Apr 23 '19 at 16:38
  • The practice problem is to implement a square root function without using the the math package so I'd like to stay consistent. This is really just a learning exercise as I'm just now using golang for the first time – Lance Apr 23 '19 at 18:38
  • The standard way to compare floats is what you're doing already, the standard way to round them is using `math.Round`. It guess the exercise is for you to figure out how to do it without the standard tools normally available to a Go developer. – Adrian Apr 23 '19 at 18:52
  • If you want a rounding function without the `math` package, see [Golang Round to Nearest 0.05](https://stackoverflow.com/questions/39544571/golang-round-to-nearest-0-05/39544897#39544897). If you want a "specialized" `abs()` function (suiting your needs), that is a trivial `if` statement... – icza Apr 23 '19 at 19:15
  • Adrian, the problem with what I'm doing above is at a certain point z and zprev alternate between certain floating point values one with an extra level of precision which ends up in an infinite loop since z and zprev never equal themselves even though the interview of values has not changed at all after a certain amount of cycles. Just figured out how to make it work by adding z to both sides in the for loop. – Lance Apr 23 '19 at 20:58

4 Answers4

4

Instead of rounding, take the difference between the two numbers you want to compare, and check that it is between -epsilon and epsilon, where epsilon is whatever you consider to be a sufficiently small difference.

Note: Unreliable equality comparisons are not go-specific; it's a universal problem with floating-point numbers.

Aasmund Eldhuset
  • 37,289
  • 4
  • 68
  • 81
2

Here's how I'd do it – giving that you don't want to use the math package at all.

package main

import "fmt"

func abs(x float64) float64 {
    if x < 0 {
        return -x
    }
    return x
}

func Sqrt(x float64) float64 {
    z := x
    var zprev float64
    for abs(zprev-z) > 1e-6 {
        zprev, z = z, z-(z*z-x)/(2*z)
    }
    return z
}

func main() {
    fmt.Println(Sqrt(2))
}

Output:

1.4142135623730951
Khalid Ali
  • 1,224
  • 1
  • 8
  • 12
0

I found that the solution to this problem was to add one of the values in the comparison to both sides of the comparison.

Below I have added z to both sides of the comparison and the comparison now works as expected.

package main

import (
    "fmt"
)


func Sqrt(x float64) float64 {
    // Need to look into float rounding in go
    z := 1.0
    zprev := 0.01
    for zprev + z != z + z {
        zprev = z
        z -= (z*z - x) /(2*z)
        fmt.Printf("z: %g\nzprev: %g\n", z, zprev)
        fmt.Println("_________________________________________")

    }
    fmt.Println("Finished")
    return z
}

Output:

z: 1.5
zprev: 1
_________________________________________
z: 1.4166666666666667
zprev: 1.5
_________________________________________
z: 1.4142156862745099
zprev: 1.4166666666666667
_________________________________________
z: 1.4142135623746899
zprev: 1.4142156862745099
_________________________________________
z: 1.4142135623730951
zprev: 1.4142135623746899
_________________________________________
z: 1.414213562373095
zprev: 1.4142135623730951
_________________________________________
z: 1.4142135623730951
zprev: 1.414213562373095
_________________________________________
Finished
1.4142135623730951
Lance
  • 165
  • 3
  • 13
  • 3
    This is _not_ a reliable solution! It happens to work in your example, but will fail in other situations. – Aasmund Eldhuset Apr 23 '19 at 21:26
  • 1
    In general, I advise against trying something arbitrary and forging ahead if it "seems to work" without having understood the underlying reason for why it _should_ consistently work (or, as it is in this case, realizing that it shouldn't). Why should `zprev + z` and `z + z` generally be equal in spite of `zprev` and `z` not being equal? They won't: it worked this time because it just so happened that the addition wiped out the tiny difference between `zprev` and `z` due to the inaccuracy that is inherent in floating-point operations. – Aasmund Eldhuset Apr 23 '19 at 21:36
  • Other initial `x` values will lead to a different pair of `zprev` and `z`, which might happen to remain unequal after the addition. – Aasmund Eldhuset Apr 23 '19 at 21:37
0

Lance, I believe by now you must have been well versed with the programming but for others like me who are still starting here is the floating comparison as suggested by @Aasmund Eldhuset.

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
    z:=float64(x/2)
    i:=1
    oldz := -1.0
    for {
        fmt.Printf(" %d => %f\n",(i),z)
        z-=(z*z-x)/(2*z)
        if diff:=(oldz-z); diff>-0.0000000001 && diff<0.0000000001{
            fmt.Println("Breaking!")
            break
        }
        oldz = z
        i+=1
    }
    return z
}

func main() {
    fmt.Println(Sqrt(2))
}

Output:

 1 => 1.000000
 2 => 1.500000
 3 => 1.416667
 4 => 1.414216
 5 => 1.414214
Breaking!
1.4142135623730951
CryptoRex
  • 76
  • 1
  • 10