比较双精度与自适应近似相等

时间:2012-05-02 18:42:13

标签: c# equals approximate

我试图制作一个自适应的“约等于”方法(用C#编写,但问题是一般的)接受两个双精度并返回一个布尔值,如果它们是“大约相等”或不是。通过自适应,我的意思是:

1.234和1.235 ==> TRUE

BUT

1.234567和1.234599 ==> FALSE

也就是说,“约等于”的准确性适应数字的准确性。

我在How do I find if two variables are approximately equals?找到了四舍五入的概念,但仍然有一个关于如何用于epsilon的开放式问题。

是否有人了解此类问题的最佳做法? 提前谢谢!

编辑: 我最初的问题没有包含足够的信息,我想要得到什么。抱歉,我的道歉。我想要一个程序,它可以将更高的精确度数字处理到更高的标准,同时对更低的精确度数字更宽松。对的更多例子是(其中'(0)'是隐含的零):

1.077和1.07(0)返回false(因为77与70非常不同)

1.000077和1.00007(0)返回false(因为77与70非常不同)

1.071和1.07(0)返回true(因为71接近70

1.000071和1.00007(0)返回true(因为71接近70)

无论实施代码如何,我都假设会有某种“容差”变量来确定什么是“非常不同”和什么是“接近”。

3 个答案:

答案 0 :(得分:5)

比较浮点数的一种方法是比较将它们分开的浮点表示的数量。这个解决方案对数字的大小无动于衷,因此您不必担心“epsilon”。

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

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

答案 1 :(得分:4)

floatdouble没有精确的思维方式。通过截断尾随零来编程伪精度......但是你不能将这个技巧用于你的目的,因为舍入错误会阻止这种技巧的可靠性。

decimal 确实跟踪小数点右边有多少位数,但这对于实现您建议的算法仍然毫无价值,因为任何引入的算法都是如此。表示性错误(例如,除以3)将倾向于最大化小数点右边的位数。

如果你想根据数据的已知精度实际具有模糊相等性,那么一种方法就是创建自己的数字类。像这样:

public class DoublePrecise
{
    public readonly double Error;
    public readonly double Value;
    public DoublePrecise(double error, double value) {
        Error = error;
        Value = value;
    }
    public static DoublePrecise operator -(DoublePrecise d1, DoublePrecise d2) {
        return new DoublePrecise(d1.Value - d2.Value, d1.Error + d2.Error);
    }
    //More stuff.
}

基本上,这是让你的代表数字像10.0±0.1。在这种情况下,如果它们的范围重叠,你会认为两个数字大致相等(尽管实际上,这会使你的平等操作意味着“可能相等”而你的不等式操作意味着“绝对不相等。”

另见Interval Arithmetic

答案 2 :(得分:0)

您可以将另一个分开,看看结果是否接近1,例如

var ratio = a / b;
var diff = Math.Abs(ratio - 1);
return diff <= epsilon;

然后你必须选择你的epsilon来决定值必须接近的程度。

但是,在您的示例中,您的第一个示例是0.08%的差异,但第二个示例是0.003%的差异,但您希望第一个为真,第二个为假。我不确定你在寻找什么,在确定问题的解决方案之前,你需要知道。 (在你能得到正确的答案之前需要正确的问题)你可能会想象“最后一个有效数字可以有所不同,但不会有更多”,但用数学术语来定义并不是那么简单。例如。应该0.49999999997等于0.5?怎么到0.500000000003?比较这些数字有什么意义?