你如何处理 golang 中的 float64 比较?

Posted

技术标签:

【中文标题】你如何处理 golang 中的 float64 比较?【英文标题】:How do you handle float64 comparisons in golang? 【发布时间】:2019-04-23 16:29:01 【问题描述】:

我正在浏览 go 并且在“练习:循环和函数”中遇到 float64 比较问题,您可以在其中编写一个函数来确定平方根。

来自示例: 计算机通常使用循环计算 x 的平方根。从猜测 z 开始,我们可以根据 z² 与 x 的接近程度来调整 z,从而产生更好的猜测:

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

我编写了一个函数,它会继续更新 z 直到值停止变化。只有 float64 比较永远不会失败,这会导致无限循环。解决这些类型问题的一种方法是四舍五入,但我不确定如何在不使用数学模块的情况下在 golang 中做到这一点。

你如何在 golang 中对 float64 数字进行四舍五入?在 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))

输出:

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
_________________________________________

在一个点之后,z 和 zprev 继续在两个仅相差一个精度点(1.414213562373095 和 1.4142135623730951)的值之间无限地交替

【问题讨论】:

为什么不想使用math 包?这就是浮点舍入函数的所在。 另一种方法是将绝对差异与给定阈值进行比较......但Abs 函数也在math 包中。 练习题是在不使用数学包的情况下实现平方根函数,所以我想保持一致。这真的只是一个学习练习,因为我刚刚第一次使用 golang 比较浮点数的标准方法是你已经在做的,标准的四舍五入方法是使用math.Round。它猜想这个练习是让你弄清楚如何在没有 Go 开发人员通常可用的标准工具的情况下做到这一点。 如果你想要一个没有math 包的舍入函数,请参阅Golang Round to Nearest 0.05。如果你想要一个“专门的”abs() 函数(满足你的需要),那是一个微不足道的if 声明...... 【参考方案1】:

取您要比较的两个数字之间的差值,而不是四舍五入,并检查它是否在-epsilonepsilon 之间,其中epsilon 是您认为足够小的差值。

注意:不可靠的相等比较不是特定于 go 的;这是浮点数的普遍问题。

【讨论】:

【参考方案2】:

这就是我的做法——假设你根本不想使用 math 包。

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))

输出:

1.4142135623730951

【讨论】:

【参考方案3】:

我发现解决这个问题的方法是将比较中的一个值添加到比较的两侧。

下面我在比较的两边都添加了 z,现在比较可以正常工作了。

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

输出:

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

【讨论】:

不是一个可靠的解决方案!它恰好在您的示例中有效,但在其他情况下会失败。 一般来说,我建议不要在没有理解它应该始终有效(或者,如在这种情况下,意识到它不应该)。尽管zprevz 不相等,为什么zprev + zz + z 通常应该相等?他们不会:这一次它起作用了,因为它恰好消除了 zprevz 之间的微小差异,因为浮点运算固有的不准确性。 其他初始x 值将导致一对不同的zprevz,在相加后可能碰巧保持不相等。【参考方案4】:

Lance,我相信您现在一定已经精通编程,但对于像我这样仍然从这里开始的其他人来说,是 @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))

输出:

 1 => 1.000000
 2 => 1.500000
 3 => 1.416667
 4 => 1.414216
 5 => 1.414214
Breaking!
1.4142135623730951

【讨论】:

以上是关于你如何处理 golang 中的 float64 比较?的主要内容,如果未能解决你的问题,请参考以下文章

你如何处理 Angular 中的函数顺序?

ApolloClient:你如何处理缓存中的规范化/嵌套?

你如何处理无状态 grails 服务中的共享数据

你如何处理 UI 自动化 iPhone 应用程序测试中的 UIPickerView?

你如何处理重复的街道后缀?

你如何处理不同的字符编码?