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

本文关键字:自适应 双精度 比较 | 更新日期: 2023-09-27 18:33:02

我正在尝试制作一个自适应的"大约相等"方法(用 C# 编写,但问题是通用的(接受两个双精度并返回布尔值,如果它们是否"大致相等"。通过自适应,我的意思是:

1.234

和 1.235 ==> 真

1.234567

和 1.234599 ==> FALSE

也就是说,"大致相等"的优先适应数字的优先。

我在如何找到两个变量是否近似相等中找到了一个舍入的概念? 但是仍然存在一个开放式问题,即对 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(

无论实现代码如何,我都假设会有某种"容忍度"变量来确定什么是"非常不同"的,什么是"接近的"。

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

比较浮点数的一种方法是比较分隔它们的浮点表示数。该解决方案对数字的大小无关紧要,因此您不必担心"epsilon"。

算法

的描述可以在这里找到(最后是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更改为int0x8000000000000000更改为0x80000000

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 这样的数字。 在这种情况下,如果两个数字的范围重叠,您将认为它们大致相等(尽管实际上,这将使您的相等运算意味着"可能相等",而您的不等式运算意味着"绝对不相等"。

另请参阅区间算术

您可以将一个除以另一个,看看结果是否接近 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呢? 比较数字有什么意义?