比较双精度与自适应近似相等
本文关键字:自适应 双精度 比较 | 更新日期: 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
更改为int
,0x8000000000000000
更改为0x80000000
。
float
和double
在你思考的方式上没有精确性。 程序通过截断尾随零来伪造精度...但是您不能将此技巧用于您的目的,因为舍入误差会阻止此类技巧可靠。
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呢? 比较数字有什么意义?