去漂浮比较

aug*_*tzf -1 floating-point go ieee-754

为了比较Go中的两个浮点数(float64)是否相等,我对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
}
Run Code Online (Sandbox Code Playgroud)

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

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

Eri*_*hil 13

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

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

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

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

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

  • 十进制数字"0.2"被转换为float64.这产生了0.200000000000000011102230246251565404236316680908203125,这是最接近float640.2的值.

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

  • 十进制数"0.3"被转换为float64.这产生了0.299999999999999988897769753748434595763683319091796875,这是最接近float640.3的值.

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

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

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

没有用于比较"相等"的浮点值的通用解决方案,因为任何这样的解决方案都不可能存在.

  • @augustzf:在这种情况下,通过检查这些位基本上没有什么用处。一般来说,任何有用的测试都可以使用正常的浮点运算来执行。如果您只是想知道浮点如何工作以及应该如何使用,而不是试图解决特定的应用程序问题,那么答案是浮点不是为此而设计的。 (2认同)

Arm*_*ani 6

不要使用的位表示,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))
}
Run Code Online (Sandbox Code Playgroud)

  • @augustzf http://floating-point-gui.de/errors/comparison/#look-out-for-edge-cases (2认同)
  • @EricPostpischil 我明白你在说什么,但“没有好的通用”对我来说似乎有点过分了。简单的almostEqual 函数可以在大多数情况下使用。如果有人不能容忍聚合的微小错误,他/她根本不应该使用浮点计算。 (2认同)
  • @ArmanOrdookhani:除此之外,显示“方法”进行浮点比较的答案通常使用不好的例子。它们使用通常不适用的任意公差,有时不正确地使用“ FLT_EPSILON”或“ DBL_EPSILON”(例如,未按所涉及数字的大小进行缩放,这仅是它们造成的误差之一),在绝对值或绝对值之间任意切换相对错误,因为作者制定了某种可行的方法,而不是由于任何操作理论而导致的,依此类推。它们是k脚,不是好的通用解决方案。 (2认同)