如何确定两个双精度浮点数是否几乎相等

3

我正在寻找一些Java代码来确定两个double数是否近似相等。我进行了大量的谷歌搜索,并找到了一些碎片化的信息,现在我把它们拼凑在这里。当使用“相对epsilon”时,我开始感到困惑。这种方法似乎是我要找的。我不想直接指定epsilon,而是想使用基于两个参数的数量级的epsilon。以下是我拼凑的代码,我需要对其进行检查。(P.S. 我只懂得足够多的数学知识以便危险。)

public class MathUtils
{
    // https://dev59.com/Nm865IYBdhLWcg3wnP6a
    // epsilon-value-when-performing-double-value-equal-comparison
    // ULP = Unit in Last Place
    public static double relativeEpsilon( double a, double b )
    {
        return Math.max( Math.ulp( a ), Math.ulp( b ) );
    }

    public static boolean nearlyEqual( double a, double b )
    {
        return nearlyEqual( a, b, relativeEpsilon( a, b ) );
    }

    // http://floating-point-gui.de/errors/comparison/
    public static boolean nearlyEqual( double a, double b, double epsilon )
    {
        final double absA = Math.abs( a );
        final double absB = Math.abs( b );
        final double diff = Math.abs( a - b );

        if( a == b )
        {
            // shortcut, handles infinities
            return true;
        }
        else if( a == 0 || b == 0 || absA + absB < Double.MIN_NORMAL )
        {
            // a or b is zero or both are extremely close to it
            // relative error is less meaningful here
            // NOT SURE HOW RELATIVE EPSILON WORKS IN THIS CASE
            return diff < ( epsilon * Double.MIN_NORMAL );
        }
        else
        {
            // use relative error
            return diff / Math.min( ( absA + absB ), Double.MAX_VALUE ) < epsilon;
        }
    }
}

1
一个ulp可能是一个太小的epsilon。 - Louis Wasserman
你有什么推荐的方法可以提高它吗? - careysb
啊,@LouisWasserman,看起来像是Klaus提到的你的东西。 - careysb
我的想法和其他人的不同。但我至少会使用一个显著常数倍的ulp?虽然坦率地说,并没有普遍适用的规则,否则每个人都已经在使用它了,大多数人都喜欢明确指定epsilon。但一般来说:误差将显着大于一个ulp,而一个ulp将显着大于“Double.MIN_NORMAL”。 - Louis Wasserman
感谢大家。我没有意识到自己陷入了一个棘手的问题中。看来我必须根据每个具体的用途来定制epsilon(小量)。 - careysb
3个回答

10

该库的fuzzyEquals()函数需要一个容差参数,而我正试图通过使用ulp来避免这一点(显然我没有做对)。 - careysb
3
图书馆要求设定公差的原因是因为正确的公差不仅取决于ulp值,还取决于考虑到迄今为止的计算所产生的舍入误差的预期范围。 - Patricia Shanahan
没错,如果任何一个浮点数或双精度浮点数是通过计算得出的,你就需要有一个公差范围,因为精度可能会丢失。例如 (2f/11 * 9) * 11; 得出的结果为18.000002,而 3f*6 得出的结果为18.0, 在比较时你需要有一个公差范围。 - Klaus Groenbaek

0

您可以使用 Apache Commons Math 中的类 org.apache.commons.math3.util.Precision。例如:

if (Precision.equals(sum, price, 0.009)) {
    // arguments are equal or within the range of allowed error (inclusive)
}

0

比较两个浮点数 a,b 的常规方法是:

if ( Math.abs(a-b) <= epsilon ) do_stuff_if_equal;
 else                       do_stuff_if_different;

Math.abs() 是绝对值函数。由于我不用JAVA编程,如果不是这种情况,您需要使用 double 变量。 epsilon 是您的差异。如上所述,ulp 对于此操作来说太小了。您需要使用对于您正在比较的值有意义的值。那么如何计算 epsilon 呢?

这有点棘手,是可以使用 a,b 的数量级,但这不是一种健壮的方法,因为如果 a,b 的指数差异太大,您很容易得到错误的结果。相反,您应该使用有意义的值。例如,如果您正在比较位置坐标,则 epsilon 应该是最小详细信息或您认为是相同点的最小距离的一部分。对于角度,应该使用足够小的最小角度,例如 1e-6 deg,但该值取决于您使用的范围和精度。对于规范化的 <-1,1> 范围,我通常使用 1e-101e-30

正如您所见,epsilon 主要取决于目标精度和大小,并且在不同情况下变化很大,因此创建一些统一的方式(如您所需的去除 epsilon)是不安全的,只会在以后导致头痛。

为了简化这个问题,我通常定义一个 _zero 常量或变量(在计算类的情况下),它可以更改。将其默认设置为对大多数情况有效的值,如果在某一时刻出现问题,则可以轻松更改它...

如果您仍然想按照自己的方式进行操作(忽略上述文本),则可以执行以下操作:

if (Math.abs(a)>=Math.abs(b)) epsilon=1e-30*Math.abs(b);
 else                         epsilon=1e-30*Math.abs(a);

但正如我所说,这可能会导致错误的结果。如果您坚持使用ulp,那么我建议使用Min而不是Max


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接