去漂浮比较

时间:2017-12-25 14:11:09

标签: go floating-point ieee-754

为了比较两个浮点数(float64)在Go中的相等性,我对IEEE 754的基本理解和浮点数的二进制表示使我认为这是一个很好的解决方案:

func Equal(a, b float64) bool {
    ba := math.Float64bits(a)
    bb := math.Float64bits(b)
    diff := ba - bb
    if diff < 0 {
        diff = -diff
    }
    // accept one bit difference
    return diff < 2
}

问题是:这是一种更通用,更精确,更有效的方法来比较两个任意大或小的浮动“几乎相等”,而不是旧的abs(diff) < epsilon黑客?我的理由是,如果一个人只允许二进制表示中的一个比特差异,那么除了严格的相等之外,比较的数字肯定不会更加相等,这显然(如评论中所指出的)可以用{{1}来检查对于花车。

注意:我已编辑问题以使其更清晰。

2 个答案:

答案 0 :(得分:9)

不,这不是比较浮点值的正确方法。

你还没有真正陈述过你的真正问题 - 你有一些理由想要比较两个浮点数,但你还没有说出它是什么。

浮点运算用于执行近似算术。在浮点运算中会出现舍入误差的累积是正常的。当以不同方式计算值时,这些错误通常会有所不同,因此不应期望浮点运算产生相同的结果。

在您的示例中,发生了以下操作:

  • 十进制数“0.1”被转换为float64(IEEE-754 64位二进制浮点数)。这产生了值0.1000000000000000055511151231257827021181583404541015625,这是最接近float64的值为0.1。

  • 十进制数字“0.2”已转换为float64。这产生了0.200000000000000011102230246251565404236316680908203125,这是最接近float64的值0.2。

  • 添加了这些内容。这产生了0.3000000000000000444089209850062616169452667236328125。除了在float64中将0.1和0.2四舍五入到最接近的值时发生的舍入误差,还包含一些额外的舍入误差,因为无法在float64中表示精确的和。

    < / LI>
  • 十进制数“0.3”已转换为float64。这产生了0.299999999999999988897769753748434595763683319091796875,这是最接近float64的值0.3。

正如您所看到的,添加0.10.2的结果累积了0.3的不同舍入误差,因此它们不相等。没有正确的平等测试会报告他们是平等的。而且,重要的是,此示例中出现的错误特定于此示例 - 浮点运算的不同序列将具有不同的错误,并且累积的错误不限于数字的低位

有些人试图通过测试差异是否小于某个小值进行比较。在某些应用程序中这可能没问题,但在您的应用程序中是否可以?我们不知道你要做什么,所以我们不知道会发生什么问题。允许小错误的测试有时会报告不正确的结果,或者是误报(因为它们接受相同的数字,如果用精确的数学计算则不相等)或假阴性(因为它们拒绝相等的数字相等,如果用精确数学)。您的应用程序中哪些错误更糟?他们中的一个会导致机器坏掉还是一个人受伤?在不知情的情况下,没有人可以建议哪些不正确的结果是可以接受的,或者即使两者都是。

此外,误差容忍度应该是多大?计算中可能发生的总误差取决于执行的操作顺序和涉及的数字。某些应用程序只有很小的最终舍入错误,而某些应用程序可能会出现大量错误。没有人更多地了解您的特定操作顺序,没有人可以提出有关使用什么价值的建议。此外,解决方案可能不是在比较数字时接受容差,而是重新设计计算以避免错误,或者至少减少错误。

没有用于比较“相等”的浮点值的通用解决方案,因为任何此类解决方案都不可能存在。

答案 1 :(得分:4)

不要使用float64的位代表,因为它在很多情况下都没有意义。只需减去两个数字即可找出它们之间的差异:

package main

import (
    "fmt"
    "math"
)

const float64EqualityThreshold = 1e-9

func almostEqual(a, b float64) bool {
    return math.Abs(a - b) <= float64EqualityThreshold
}

func main() {
    a := 0.1
    b := 0.2
    fmt.Println(almostEqual(a + b, 0.3))
}