【问题标题】:How should I compare these doubles to get the desired result?我应该如何比较这些双打以获得所需的结果?
【发布时间】:2013-08-06 13:12:22
【问题描述】:

我在这里有一个简单的示例应用程序,我将double 变量相乘和相加,然后将它们与预期结果进行比较。在这两种情况下,结果都等于预期结果,但是当我进行比较时它失败了。

static void Main(string[] args)
{
    double a = 98.1;
    double b = 107.7;
    double c = 92.5;
    double d = 96.5;

    double expectedResult = 88.5;
    double result1 = (1*2*a) + (-1*1*b);
    double result2 = (1*2*c) + (-1*1*d);            

    Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", a, b, result1, expectedResult == result1));
    Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", c, d, result2, expectedResult == result2));

    Console.Read();
}

这是输出:

2x98.1 - 107.7 = 88.5
Equal to 88.5? False

2x92.5 - 96.5 = 88.5
Equal to 88.5? True

在这两种情况下,我都需要能够捕捉到它实际上是 True。我该怎么做?

【问题讨论】:

    标签: c# .net comparison double precision


    【解决方案1】:

    浮点数通常不包含数学告诉我们的确切值,因为它们存储数字的方式。

    要进行可靠的比较,您需要允许一些差异:

    private const double DoubleEpsilon = 2.22044604925031E-16;
    
    /// <summary>Determines whether <paramref name="value1"/> is very close to <paramref name="value2"/>.</summary>
    /// <param name="value1">The value1.</param>
    /// <param name="value2">The value2.</param>
    /// <returns><c>true</c> if <paramref name="value1"/> is very close to value2; otherwise, <c>false</c>.</returns>
    public static bool IsVeryCloseTo(this double value1, double value2)
    {
        if (value1 == value2)
            return true;
    
        var tolerance = (Math.Abs(value1) + Math.Abs(value2)) * DoubleEpsilon;
        var difference = value1 - value2;
    
        return -tolerance < difference && tolerance > difference;
    }
    

    还请务必阅读this blog post

    【讨论】:

    • +1 但是我能问你这个公式是从哪里得到的吗? Silverlight 控制工具包?还是伏都教背后有一些真正的数学?因为...stackoverflow.com/questions/2411392/…
    • @xanatos:好问题。它来自我多年未更改的辅助库之一。我很确定我是从某处网上得到的。我正在努力追查源头。
    • @xanatos:关于常量DoubleEpsilon,参见en.wikipedia.org/wiki/Machine_epsilon。其余的还不确定。
    • 你的常数是Math.Pow(2, -52),四舍五入到15位有效小数。 编辑:要获得准确的数字,即负 52 的 2,请在 const 声明中的 E 之前插入一个额外的数字 3。跨度>
    • @JeppeStigNielsen:这是一个非常有趣的话题。不幸的是,我找不到原始来源,也找不到任何其他证明+ 10 的来源。因此,我调整了答案中的代码。
    【解决方案2】:

    如果您需要更高的精度(金钱等),请使用decimal

    var a = 98.1M;
    var b = 107.7M;
    var c = 92.5M;
    var d = 96.5M;
    
    var expectedResult = 88.5M;
    var result1 = (2 * a) + (-1 * b);
    var result2 = (2 * c) + (-1 * d);
    
    Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", a, b, result1, expectedResult == result1));
    Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", c, d, result2, expectedResult == result2));
    

    输出:

    2x98.1 - 107.7 = 88.5
    Equal to 88.5? True
    
    2x92.5 - 96.5 = 88.5
    Equal to 88.5? True
    

    【讨论】:

    • decimalNOT 定点。它仍然是浮点数。但是内部表示不是二进制而是十进制,这就是为什么它可以很好地处理像 0.1 这样的“正常”数字。简单证明:Assert.Equal(1/3m * 2, 2/3m); // will fail
    • @DanielHilgarth 我同意decimal 不是定点的。但证明无效,因为对于定点类型,断言也会失败。例如,在点后有三个固定小数,一个值为0.666,而另一个值为0.667。 (我假设一个定点算术,其中除法四舍五入到最接近的可表示值,而不仅仅是截断。)
    【解决方案3】:

    浮点数在内存中的表示方式存在问题。

    您应该阅读此内容以更好地了解正在发生的事情:What Every Computer Scientist Should Know About Floating-Point Arithmetic

    【讨论】:

      【解决方案4】:

      只需将四舍五入更改为 2 级,这将给出 TRUE

      double result1 =Math.Round ( (1 * 2 * a) + (-1 * 1 * b),2);
      

      【讨论】:

        【解决方案5】:

        使用 Math.Round() 会将 result1 舍入为正确的小数

        result1 = Math.Round(result1, 1);
        

        【讨论】:

          【解决方案6】:

          使用调试器,

          result1=88.499999999999986;
          expectedResult = 88.5
          

          所以当使用双精度时,这些是不相等的。

          【讨论】:

            【解决方案7】:

            整个学派都反对使用Double.Epsilon 和类似的数字...

            我认为他们使用这个:(取自 https://stackoverflow.com/a/2411661/613130,但通过检查 IsNaNIsInfinity 进行了修改,建议 here by nobugz

            public static bool AboutEqual(double x, double y)
            {
                if (double.IsNaN(x)) return double.IsNaN(y); 
                if (double.IsInfinity(x)) return double.IsInfinity(y) && Math.Sign(x) == Math.Sign(y);
            
                double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
                return Math.Abs(x - y) <= epsilon;
            }
            

            1E-15“幻数”基于 doubles 的精度略高于 15 位这一事实。

            我会为你的数字添加它返回 true :-)

            【讨论】:

              猜你喜欢
              • 2015-02-09
              • 2013-05-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-11-19
              • 1970-01-01
              相关资源
              最近更新 更多