比较两个十进制数

6

我想在c#中比较两个小数,并带有一定的误差范围。有人能指出以下代码存在的问题吗?请注意,我只关心小数点后6位,之后的值可以忽略。

var valOne = decimal.Round(valueOne, 6);
var valTwo = decimal.Round(valueTwo, 6);
var difference = Math.Abs(valOne - valTwo);
if (difference > 0.0000001m) {
   Console.WriteLine("Values are different");
}
else {
    Console.WriteLine("Values are equal");
}

或者有更好的方法吗。

2
这一行代码 var difference = Math.Abs(valOne, valTwo); 无法编译。应该是 var difference = Math.Abs(valOne - valTwo);。还有 decimal.Roung 应该是 decimal.Round - Tom Chantler
抱歉,那是一个打错的字,但上面的代码仅供参考。 - tangokhi
这个问题比这个问题晚问了一些,但基本相同,并且有一些很好的材料-https://dev59.com/Xm865IYBdhLWcg3wUs7z - vapcguy
这个回答解决了你的问题吗?如何在C#中正确比较十进制值? - Michael Freidgeim
7个回答

6

如果你将数值舍入到小数点后6位,那么你的epsilon值太小了。这两个值之间可以不同的最小值为0.000001。

例如:

var valOne = Decimal.Round(1.1234560M, 6);    // Gives 1.123456
var valTwo = Decimal.Round(1.1234569M, 6);    // Gives 1.123457

if (Math.Abs(valOne - valTwo) >= 0.000001M)
{
    Console.WriteLine("Values differ");
}
else
{
    Console.WriteLine("Values are the same");
}

以上解决方案将打印“Values differ”,尽管考虑到我们只关心6位小数,这些值是相同的。因此,在上述两个数字中,超过6位小数的仅为:123456,只有最后一位数字不同。 我无法想出一种有效地忽略第6位小数后的数字的方法。 - tangokhi
如果你想忽略小数点后第六位以后的数字,那么你就不应该使用Round函数。你可以这样做:valOne = valOne - (valOne % 0.000001);来截断你不感兴趣的数字。或者你可以乘以10^6,然后转换为整数来消除额外的精度,再次除以10^6。 - Dave R.

1
以下对我有效:

var valueOne = 1.1234563M;
var valueTwo = 1.1234567M;

var diff = Math.Abs(valueOne - valueTwo);
//Console.WriteLine(diff);

if(diff > 0.0000003M)
{
    Console.WriteLine("diff");
}
else
{
    Console.WriteLine("equal");
}

上面将显示“diff”。
如果您更改为var valueOne = 1.1234565M;,差异将小于阈值,因此它将显示“equal”。
然后根据您的需求进行RoundTruncate
编辑:@tangokhi刚注意到您的答案! 您是正确的..忽略我的回复。

1
这个答案基于这里的最高票答案: C#浮点数比较函数
需要考虑到边缘情况,不能直接进行比较,如此处所示。基本上是因为十进制数可以相等,但代码可能并不认为它们相等。
float a = 0.15 + 0.15
float b = 0.1 + 0.2
if (a == b) { ... } // can be false!
if (a >= b) { ... } // can also be false!

你需要指定要比较数字的接近程度。链接中给出的答案将其称为“Epsilon”,但他们没有详细说明这是位值、范围还是仅仅是给定数字的增量。
以下函数中的“Epsilon”将在给定差距范围内比较两个数字。例如,如果您想使用上面的示例并使其返回true,则应该在彼此之间比较0.1,而不是默认情况下在比较0.30和0.3时比较0.01。
    public static bool nearlyEqual(double a, double b, double epsilon)
    {
        double absA = Math.Abs(a);
        double absB = Math.Abs(b);
        double diff = Math.Abs(a - b);

        if (a == b)
        { 
            // shortcut, handles infinities
            return true;
        }
        else if (a == 0 || b == 0 || diff < Double.Epsilon)
        {
            // a or b is zero or both are extremely close to it
            // relative error is less meaningful here
            return diff < epsilon;
        }
        else
        { 
            // use relative error
            return diff / (absA + absB) < epsilon;
        }
    }

假设您拥有一个字典,其中包括物品ID和浮点小数(双精度)数字,就像一堆纬度一样...
 Dictionary<int, double> cityLatPoints = new Dictionary<int, double>();

如果您想知道一个纬度是否靠近这些点之一......以下是您可以执行的操作:

double epsilon = 0.000005;
List<int> possLatCityIds = new List<int>();  // stores your matching IDs for later
double dblLat = 39.59833333;  // hard-coded value here, but could come from anywhere

// Possible Latitudes
foreach (KeyValuePair<int, double> kvp in cityLatPoints)
{
    if (nearlyEqual(kvp.Value, dblLat, epsilon))
    {
        //Values are the same or similar
        possLatCityIds.Add(kvp.Key);  // ID gets added to the list
    }
}

给出的例子,它看起来像这样:

对于给定的示例,它应该是这样的:

decimal valOne = decimal.Round(valueOne, 6);
decimal valTwo = decimal.Round(valueTwo, 6);
double dblOne = Convert.ToDouble(valOne);
double dblTwo = Convert.ToDouble(valTwo);
double epsilon = 0.0000001;

if (nearlyEqual(dblOne, dblTwo, epsilon))
{
    Console.WriteLine("Values are equal");
}
else
{
    Console.WriteLine("Values are different");
}

0

我认为如果不使用Round函数,这个解决方案就很好。

var valOne = 1.1234560M; // Decimal.Round(1.1234560M, 6);  Don't round.
var valTwo = 1.1234569M; // Decimal.Round(1.1234569M, 6);  Don't round

if (Math.Abs(valOne - valTwo) >= 0.000001M) // Six digits after decimal in epsilon
{
    Console.WriteLine("Values differ");
}
else
{
    Console.WriteLine("Values are the same");
}

如上所述,对于六位小数,两位小数之间可以相差的最小金额为0.000001M。小于此金额的可安全忽略。我认为这个解决方案是可行的,但如果有人认为我漏掉了什么,我会感激你的帮助。

感谢大家


6
将来,我建议接受Dave的答案,并在上面留下评论,说明你最终的解决方案省略了四舍五入,而不是发布新的答案。一般来说,仅当你的解决方案与已发布的任何其他解决方案大相径庭时才应该发布答案。 - Matt

0

修正拼写错误 "var valTwo = decimal.Roung(valueTwo, 6);" 应该是 decimal.Round(....

你也可以使用 Decimal.Equals(dec1, dec2) 或 Decimal.Compare(dec1, dec2) 来比较十进制数。


你认为 Decimal.Equals 或 Decimal.Compare 会处理 epsilon 值(0.0000001m),因此不需要进行以下检查吗:if (difference > 0.0000001m) - tangokhi

0
你可以创建一个函数,然后像这样做些事情。
public static bool Check(decimal first, decimal second, decimal margin) 
{ 
    return Math.Abs(first - second) <= margin; 
}

根据值是否小于或等于,它将返回true或false。


1
那么如果差异小于边际会怎样?似乎不对。 - Esben Skov Pedersen

0

该方法用于比较两个指定的十进制值。 语法:public static int Compare (decimal a1, decimal a2);

参数: a1:此参数指定要比较的第一个值。 a2:此参数指定要比较的第二个值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接