Language agnostic 我们是否应该将浮点数的相等性与*相对*错误进行比较?

Language agnostic 我们是否应该将浮点数的相等性与*相对*错误进行比较?,language-agnostic,math,comparison,floating-point,Language Agnostic,Math,Comparison,Floating Point,到目前为止,我已经看到很多关于浮点数相等的帖子。对于“我们应该如何决定x和y是否相等”这样的问题,标准答案是 abs(x-y)

到目前为止,我已经看到很多关于浮点数相等的帖子。对于“我们应该如何决定x和y是否相等”这样的问题,标准答案是

abs(x-y)
其中ε是一个固定的小常数。这是因为“操作数”x和y通常是涉及舍入误差的某些计算的结果,因此标准相等运算符==不是我们的意思,我们真正应该问的是x和y是否接近,而不是相等

现在,我觉得如果x“几乎等于”y,那么x*10^20也应该“几乎等于”y*10^20,在这个意义上,相对误差应该是相同的(但是“相对”什么?)。但有了这些大数字,上述测试将失败,即该解决方案无法“扩展”

你将如何处理这个问题?我们应该重新缩放数字还是重新缩放ε?怎么用? (还是我的直觉错了?)


这是一个答案,但我不喜欢它被接受的答案,因为重新解释演员阵容对我来说似乎有点棘手,我不明白发生了什么。请尝试提供一个简单的测试。

这取决于具体的问题领域。是的,在一般情况下,使用相对误差更为正确,但由于它涉及额外的浮点除法,因此效率可能会大大降低。如果您知道问题中数字的近似比例,则使用绝对误差是可以接受的


概述了一些比较浮动的技术。它还讨论了一些重要的问题,如次正规、无穷大和NaN。这是一本很棒的书,我强烈建议你通读一遍。

问题是,对于非常大的数字,与epsilon相比会失败

也许更好(但更慢)的解决方案是使用除法,例如:

div(max(a, b), min(a, b)) < eps + 1
div(最大(a,b),最小(a,b))

现在“错误”将是相对的。

作为替代解决方案,为什么不将数字四舍五入或截断,然后进行直接比较呢?通过预先设置有效位数,您可以确定该范围内的准确度。

使用相对误差至少没有使用绝对误差那么糟糕,但由于舍入问题,它对接近零的值存在微妙的问题。一个远非完美但有点稳健的算法结合了绝对和相对误差方法:

boolean approxEqual(float a, float b, float absEps, float relEps) {
    // Absolute error check needed when comparing numbers near zero.
    float diff = abs(a - b);
    if (diff <= absEps) {
        return true;
    }

    // Symmetric relative error check without division.
    return (diff <= relEps * max(abs(a), abs(b)));
}
布尔近似相等(浮点a、浮点b、浮点absEps、浮点relEps){
//比较接近零的数字时需要进行绝对误差检查。
浮差=abs(a-b);

如果(diff在代码比较值的大部分时间,它这样做是为了回答某种问题。例如:

  • 如果我知道一个函数在给定X值时返回了什么,我能假设它在给定Y时返回相同的东西吗

  • 如果我有一种计算缓慢但准确的函数的方法,我愿意接受一些不准确来换取速度,我想测试一个似乎符合要求的候选函数,该函数的输出是否足够接近已知的准确函数,从而被认为是“正确的”

  • 为了回答第一个问题,理想情况下,代码应该对值进行逐位比较,除非语言支持2009年添加到IEEE-754的新操作符,这可能比理想情况下效率低。为了回答第二个问题,应该定义所需的准确度并进行测试


    我不认为通用方法有多大优点,因为不同的应用对绝对和相对公差的要求不同,这取决于测试应该回答的确切问题。

    谢谢。论文还解释了rud背后的动机e转换为int(虽然在普通代码中我会选择可理解性并使用所有浮点解决方案中的一种:)上面链接的更新版本正是,它是相对于a和b之间的最小值,不是吗?嗯。小心被零除:)注意它们的符号。亚当答案中的文章建议比较相对绝对最大值。舍入和截断效果很差。如果我们舍入(到最近的值)对于三个数字,1.499999999和1.5000000000001将比较ad不同,尽管它们非常接近。如果我们截断,则1.99999999999和2.00000000000001将比较不同,尽管它们非常接近。任何舍入或截断方案都会有这样的尖点。任何解决方案都必须从减去数字和然后决定差异是否足够大,以至于有意义。@BruceDawson 1.49999999999和1.500000000000001四舍五入到3位,将比较为相等…(均为1.5)嗯。我不知道为什么我会给出这些数字作为例子,因为你是对的,它们不像我所说的那样有效。实际问题是,你可以有两个任意接近的数字,它们彼此取整,因此当它们应该相等时,比较就不同了。对于取整到最近值:1.50500000000001 1.50499999999999当四舍五入到三位,这两个数字分别是1.51和1.50,尽管它们之间的距离不到十亿分之一。任何四舍五入方案都会在接近顶点的数字上遇到这个问题。这就是为什么舍入和比较被打破的原因。@BruceDawson:round和compare对于某些等价类(例如hashset/map/dictionary)可能很有用如果将接近阈值的项目多次添加到集合中。请参阅。
    boolean approxEqual(float a, float b, float absEps, float relEps) {
        // Absolute error check needed when comparing numbers near zero.
        float diff = abs(a - b);
        if (diff <= absEps) {
            return true;
        }
    
        // Symmetric relative error check without division.
        return (diff <= relEps * max(abs(a), abs(b)));
    }