【问题标题】:How do I find if two variables are approximately equals?如何确定两个变量是否近似相等?
【发布时间】:2011-03-26 03:35:49
【问题描述】:

我正在编写单元测试来验证数据库中的计算,并且有很多舍入和截断以及意味着有时数字略有偏差的东西。

在验证时,我发现很多时候事情会通过,但会说失败 - 例如,数字为 1,我得到 0.999999

我的意思是,我可以将所有内容四舍五入,但由于我使用了大量随机样本,最终我会得到这样的结果

10.5 10.4999999999

一个会舍入到 10,另一个会舍入到 11。

如果我需要一些近似正确的东西,我应该如何解决这个问题?

【问题讨论】:

  • 比较浮点数,2012 版:randomascii.wordpress.com/2012/02/25/… - 不是 C# 版本,您将获得所需的一切
  • Math.Round() 从 Framework 1.1 开始提供重载,允许您指定位数,并且从 2.0 开始,您可以指定如何处理中点舍入。

标签: c# .net-3.5


【解决方案1】:

定义一个容差值(又名“epsilon”或“delta”),例如 0.00001,然后用它来比较差异,如下所示:

if (Math.Abs(a - b) < delta)
{
   // Values are within specified tolerance of each other....
}

您可以使用Double.Epsilon,但您必须使用乘数。

更好的是,编写一个扩展方法来做同样的事情。我们的单元测试中有类似Assert.AreSimiliar(a,b) 的内容。

Microsoft 的 Assert.AreEqual() 方法有一个需要 delta 的重载:public static void AreEqual(double expected, double actual, double delta)

NUnit 还为其Assert.AreEqual() 方法提供重载,允许提供增量。

【讨论】:

  • 抱歉,我的意思是如果ab 的值非常small,那将会失败。那么绝对差异会很小,而相对差异可能会很大。 (0.00000020 - 0.00000001) &lt; delta,但 ab 大 20 倍。
【解决方案2】:

您可以提供一个函数,该函数包含一个用于两个值之间可接受差异的参数。例如

// close is good for horseshoes, hand grenades, nuclear weapons, and doubles
static bool CloseEnoughForMe(double value1, double value2, double acceptableDifference)
{
    return Math.Abs(value1 - value2) <= acceptableDifference; 
}

然后调用它

double value1 = 24.5;
double value2 = 24.4999;

bool equalValues = CloseEnoughForMe(value1, value2, 0.001);

如果你想稍微专业一点,可以调用函数ApproximatelyEquals 或类似的东西。

static bool ApproximatelyEquals(this double value1, double value2, double acceptableDifference)

【讨论】:

  • +1 表示正确答案,+1 表示建议扩展,-1 表示不够势利,无法使用希腊字母。 :-)
  • 我个人称它为CloseEnoughForGovernmentWork()
【解决方案3】:

我没有检查添加了哪个 MS 测试版本,但在 v10.0.0.0 中 Assert.AreEqual 方法具有接受 delta 参数并进行近似比较的重载。

https://msdn.microsoft.com/en-us/library/ms243458(v=vs.140).aspx

//
// Summary:
//     Verifies that two specified doubles are equal, or within the specified accuracy
//     of each other. The assertion fails if they are not within the specified accuracy
//     of each other.
//
// Parameters:
//   expected:
//     The first double to compare. This is the double the unit test expects.
//
//   actual:
//     The second double to compare. This is the double the unit test produced.
//
//   delta:
//     The required accuracy. The assertion will fail only if expected is different
//     from actual by more than delta.
//
// Exceptions:
//   Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException:
//     expected is different from actual by more than delta.
public static void AreEqual(double expected, double actual, double delta);

【讨论】:

    【解决方案4】:

    比较浮点数的一种方法是比较有多少浮点表示将它们分开。此解决方案与数字的大小无关,因此您不必担心其他答案中提到的“epsilon”的大小。

    算法的描述可以在here(最后是AlmostEqual2sComplement函数)找到,这是我的C#版本。

    更新: 提供的链接已过时。包含一些改进和错误修复的新版本是here

    public static class DoubleComparerExtensions
    {
        public static bool AlmostEquals(this double left, double right, long representationTolerance)
        {
            long leftAsBits = left.ToBits2Complement();
            long rightAsBits = right.ToBits2Complement();
            long floatingPointRepresentationsDiff = Math.Abs(leftAsBits - rightAsBits);
            return (floatingPointRepresentationsDiff <= representationTolerance);
        }
    
        private static unsafe long ToBits2Complement(this double value)
        {
            double* valueAsDoublePtr = &value;
            long* valueAsLongPtr = (long*)valueAsDoublePtr;
            long valueAsLong = *valueAsLongPtr;
            return valueAsLong < 0
                ? (long)(0x8000000000000000 - (ulong)valueAsLong)
                : valueAsLong;
        }
    }
    

    如果您想比较浮点数,请将所有double 更改为float,将所有long 更改为int 并将0x8000000000000000 更改为0x80000000

    使用representationTolerance 参数,您可以指定允许的错误大小。较高的值意味着接受较大的错误。我通常使用值 10 作为默认值。

    【讨论】:

    • 比较零和非常小的负值怎么样。似乎它不起作用。例如比较 0 和 -1.1102230246251565E-16
    • 提供的链接已过时。新位置是randomascii.wordpress.com/2012/02/25/…
    • 我不明白您为什么要从0x800... 中减去负值,您能详细说明一下吗?
    • @m93a 来自the original article:“处理负数的更一般的方法是调整它们,以便它们按字典顺序排列为二进制补码整数而不是有符号幅度整数。这是通过以下方式完成的检测负数并从 0x80000000 中减去它们。这将负零映射到整数零表示,并使得最小的负数由负一表示,并从那里向下。"
    【解决方案5】:

    问题是在单元测试中如何断言某些东西几乎相等。通过使用内置的Assert.AreEqual 函数,您可以断言某些东西几乎相等。例如:

    Assert.AreEqual(expected: 3.5, actual : 3.4999999, delta:0.1);

    此测试将通过。问题解决了,无需编写自己的函数!

    【讨论】:

    • 提供你自己的答案,不要抄袭别人。特别是如果另一个在同一页面上:-p
    【解决方案6】:

    在 NUnit 中,我喜欢这种形式的清晰性:

    double expected = 10.5;
    double actual = 10.499999999;
    double tolerance = 0.001;
    Assert.That(actual, Is.EqualTo(expected).Within(tolerance));
    

    【讨论】:

      猜你喜欢
      • 2021-11-15
      • 2022-12-04
      • 1970-01-01
      • 2015-07-27
      • 2018-08-31
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多