我该如何进行浮点比较?

Mik*_*ley 73 language-agnostic floating-point comparison

我正在编写一些代码,其中包含以下内容:

double a = SomeCalculation1();
double b = SomeCalculation2();

if (a < b)
    DoSomething2();
else if (a > b)
    DoSomething3();
Run Code Online (Sandbox Code Playgroud)

然后在其他地方我可能需要做平等:

double a = SomeCalculation3();
double b = SomeCalculation4();

if (a == 0.0)
   DoSomethingUseful(1 / a);
if (b == 0.0)
   return 0; // or something else here
Run Code Online (Sandbox Code Playgroud)

简而言之,我有很多浮点数学正在进行,我需要对条件进行各种比较.我无法将其转换为整数数学,因为在这种情况下这样的事情毫无意义.

我以前读过浮点比较可能不可靠,因为你可以做这样的事情:

double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
    Console.WriteLine("Oh no!");
Run Code Online (Sandbox Code Playgroud)

简而言之,我想知道:我如何可靠地比较浮点数(小于,大于,相等)?

我使用的数字范围大致是从10E-14到10E6,所以我确实需要使用小数字和大数字.

我已将此标记为语言无关,因为无论我使用何种语言,我都对如何实现此目标感兴趣.

Mic*_*rdt 64

除非你在浮点/双精度限制的边缘工作,否则比较大/小并不是一个真正的问题.

对于"模糊等于"的比较,这个(Java代码应该很容易适应)是我在 经过大量工作并考虑到许多批评之后为浮点指南提出的:

public static boolean nearlyEqual(float a, float b, float epsilon) {
    final float absA = Math.abs(a);
    final float absB = Math.abs(b);
    final float diff = Math.abs(a - b);

    if (a == b) { // shortcut, handles infinities
        return true;
    } else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
        // a or b is zero or both are extremely close to it
        // relative error is less meaningful here
        return diff < (epsilon * Float.MIN_NORMAL);
    } else { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}
Run Code Online (Sandbox Code Playgroud)

它配备了一个测试套件.你应该立即解除任何没有的解决方案,因为它实际上保证在一些边缘情况下失败,比如有一个值0,两个非常小的值反对零或无穷大.

另一种选择(参见上面的链接以获取更多详细信息)是将浮点数的位模式转换为整数并接受固定整数范围内的所有内容.

在任何情况下,可能没有任何解决方案适合所有应用程序.理想情况下,您可以使用涵盖实际用例的测试套件来开发/调整自己的应用程序.

  • 嗯.你有一个测试`else if(a*b == 0)`,但是你对同一行的评论是'a或b或两者都是零'.但这两个不同的东西不是吗?例如,如果`a == 1e-162`和`b == 2e-162`则条件'a*b == 0`将为真. (2认同)

P-G*_*-Gn 14

TL; DR

  • 使用以下函数代替当前接受的解决方案,以避免在某些限制情况下出现一些不良结果,同时可能更有效.
  • 了解您对数字的预期不精确度,并在比较函数中相应地提供它们.
bool nearly_equal(
  float a, float b,
  float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
  // those defaults are arbitrary and could be removed
{
  assert(std::numeric_limits<float>::epsilon() <= epsilon);
  assert(epsilon < 1.f);

  if (a == b) return true;

  auto diff = std::abs(a-b);
  auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
  return diff < std::max(relth, epsilon * norm);
}
Run Code Online (Sandbox Code Playgroud)

图形,好吗?

比较浮点数时,有两种"模式".

第一个是在相对模式,其中的区别xy相对认为它们的振幅|x| + |y|.在2D中绘图时,它给出以下轮廓,其中绿色表示相等xy.(epsilon为了便于说明,我拿了0.5分).

在此输入图像描述

相对模式是用于"正常"或"足够大"浮点值的模式.(稍后会详细介绍).

第二个是绝对模式,当我们简单地将它们的差异与固定数字进行比较时.它给出了以下轮廓(再次使用epsilon0.5和1 relth的1作为插图).

在此输入图像描述

这种绝对比较模式用于"微小"浮点值.

现在的问题是,我们如何将这两种反应模式拼接在一起.

在Michael Borgwardt的回答中,转换是基于价值diff,应该低于relth(Float.MIN_NORMAL在他的回答中).此开关区域在下图中以阴影线显示.

在此输入图像描述

因为relth * epsilon它更小relth,绿色补丁不会粘在一起,这反过来又给解决方案带来了不好的属性:我们可以找到这样的数字三元组,x < y_1 < y_2但是x == y2却如此x != y1.

在此输入图像描述

举一个惊人的例子:

x  = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32
Run Code Online (Sandbox Code Playgroud)

我们有x < y1 < y2,实际上y2 - x是比2000倍大y1 - x.然而,使用当前的解决方案,

nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
Run Code Online (Sandbox Code Playgroud)

相反,在上面提出的解决方案中,切换区域基于值|x| + |y|,其由下面的阴影方块表示.它确保两个区域连接正常.

在此输入图像描述

此外,上面的代码没有分支,这可能更有效.认为如操作maxabs,其中先验需要支化,往往具有专用组装说明.出于这个原因,我认为这种方法优于另一种解决方案,这将是解决迈克尔的nearlyEqual改变从交换机diff < relthdiff < eps * relth,那么这将产生基本相同的反应模式.

在相对和绝对比较之间切换的位置?

在这些模式之间进行切换relth,这FLT_MIN在接受的答案中进行.这个选择意味着表示float32是限制浮点数的精度.

这并不总是有意义的.例如,如果您比较的数字是减法的结果,则可能在某个范围内的某些内容FLT_EPSILON更有意义.如果它们是减去数字的平方根,则数值不精确可能更高.

当你考虑比较浮点数时,这是相当明显的0.在这里,任何相对比较都会失败,因为|x - 0| / (|x| + 0) = 1.因此,当x你的计算不精确时,比较需要切换到绝对模式- 很少是低到FLT_MIN.

这是引入上述relth参数的原因.

此外,通过不乘relthepsilon,这个参数的解释很简单,对应的数值精度,我们希望这些数字的水平.

数学隆隆声

(留在这里主要是为了我自己的乐趣)

更一般地说,我假设一个行为良好的浮点比较运算符=~应该具有一些基本属性.

以下是相当明显的:

  • 自平等: a =~ a
  • 对称性:a =~ b暗示b =~ a
  • 反对的不变性:a =~ b暗示-a =~ -b

(我们没有a =~ bb =~ c暗示a =~ c,=~不是等价关系).

我将添加以下特定于浮点比较的属性

  • if a < b < c,则a =~ c暗示a =~ b(更接近的值也应该相等)
  • 如果a, b, m >= 0那么a =~ b暗示a + m =~ b + m(具有相同差异的较大值也应该相等)
  • 如果0 <= ? < 1那么a =~ b暗示?a =~ ?b(也许不那么明显的争论).

这些属性已经对可能的近似相等函数提供了强有力的约束.上面提出的功能验证了它们.也许缺少一个或几个明显的属性.

当一个想到的=~是平等关系的家庭=~[?,t]的参数?relth,一个还可以添加

  • 如果?1 < ?2那么a =~[?1,t] b暗示a =~[?2,t] b(给定容差的等式意味着在更高的容差下相等)
  • 如果t1 < t2那么a =~[?,t1] b暗示a =~[?,t2] b(给定不精确的等式意味着在更高的不精确度下相等)

建议的解决方案也验证了这些.

  • 这是一个很好的答案! (3认同)
  • @anneb是的,它可以是+INF。 (3认同)
  • c++ 实现问题: ```(std::abs(a) + std::abs(b))``` 是否可以大于 ```std::numeric_limits&lt;float&gt;::max()``` ? (2认同)

小智 13

我有浮点数比较的问题A < BA > B 这里是什么似乎工作:

if(A - B < Epsilon) && (fabs(A-B) > Epsilon)
{
    printf("A is less than B");
}

if (A - B > Epsilon) && (fabs(A-B) > Epsilon)
{
    printf("A is greater than B");
}
Run Code Online (Sandbox Code Playgroud)

晶圆厂 - 绝对价值 - 如果它们基本上是平等的,则需要处理.

  • 如果您进行第一个测试“if (A - B &lt; -Epsilon)”,则根本不需要使用“fabs” (2认同)

小智 10

我们必须选择容差级别来比较浮点数.例如,

final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
    Console.WriteLine("Oh yes!");
Run Code Online (Sandbox Code Playgroud)

一个说明.你的例子很有趣.

double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
    Console.WriteLine("Oh no!");
Run Code Online (Sandbox Code Playgroud)

这里有些数学

a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.

1/3 != 1
Run Code Online (Sandbox Code Playgroud)

哦,是的..

你的意思是

if (b != 1)
    Console.WriteLine("Oh no!")
Run Code Online (Sandbox Code Playgroud)