【问题标题】:comparing doubles with adaptive approximately equal比较与自适应近似相等的双打
【发布时间】:2012-05-02 18:42:13
【问题描述】:

我正在尝试制作一个自适应的“大约相等”方法(用 C# 编写,但问题很笼统)接受两个双精度数并返回一个布尔值,如果它们“大约相等”或不“大约相等”。我所说的自适应是指:

1.234 和 1.235 ==> 正确

但是

1.234567 和 1.234599 ==> 错误

即“大约相等”的精度适应数字的精度。

我在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)

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

【问题讨论】:

  • 如何确定数字的精度?

标签: c# equals approximate


【解决方案1】:

比较浮点数的一种方法是比较有多少浮点表示将它们分开。此解决方案与数字的大小无关,因此您不必担心“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

【讨论】:

  • 我喜欢这种方法;但是,为什么我不能在不使用指针和地址的情况下将 double 类型转换为 long 类型?
  • 将 double 转换为 long 只会去掉小数点。 ToBits2Complemet 中你想要的是二进制表示(符号、指数和有效位)。
  • 当我实现这一点时,我注意到高精度数字与低精度数字的处理方式不同。恐怕我最初的问题问得不好。对于那个很抱歉!感谢您提供了一个非常有趣的(并且给出了我最初的问题是正确的)答案!
  • 比较零和非常小的负值怎么样。似乎它不起作用。例如比较 0 和 -1.1102230246251565E-16
【解决方案2】:

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

【讨论】:

  • 有趣。这是否意味着刚刚初始化的 double 右侧有非零值?似乎计算机会将我存储变量的内存“归零”。似乎应该有一种方法来计算一个适应数字精度的 epsilon 或 return(Math.ABS(d1-d2)&lt;=adaptiveEpsilon) 中的东西。感谢您的反馈。我相对缺乏经验,知道双打如何存储在内存中非常有帮助!
  • @chessofnerd:是的,如果您将双精度值初始化为在基数 2 中不可表示的值。在 .Net 4.0 中,const double oops = 1.1f; Trace.WriteLine(oops); 输出 1.10000002384186。当然,这个数字仍然是个谎言。程序存储的号码是1.10000002384185791015625DoubleConverter 会告诉你。另一方面,decimal yay = 1.1m恰好等于 1.1。
  • 阅读Binary Floating Point in .Net 了解双打如何存储在内存中。在“浮点数在内存中到底是什么样子?”中回答了这个具体问题。部分,但我建议您完整阅读这篇文章。请注意,本文是关于二进制浮点的(floatdouble)。 This companion article 大约是 Decimal
  • 谢谢!那是一篇非常的好文章。如果我理解正确,数据块中不包含垃圾(它被“清零”),但看似无害的以 10 为基数的数字(如 1.1)不能用二进制“很好”表示(它与 1/3 有相同的问题以十为底)。对吗?
【解决方案3】:

你可以将一个除以另一个,看看结果是否接近一,例如

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

然后你只需要选择你的 epsilon 来决定这些值必须有多接近。

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

【讨论】:

  • 我建议不要使用这种算法,因为它不是对称的。更安全的变化是始终采用较小数字和较高数字之间的比率(反之亦然)。另请注意,如果 b 为 0,此算法将失败,如果 a 和 b 都近似为 0,但在 0 的另一侧,则此算法将返回 false。
猜你喜欢
  • 1970-01-01
  • 2012-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多