Comparing two decimals

2019-06-27 04:29发布

问题:

I want to compare two decimals in c# with some margin of error. Can anyone point out problem with the following code. Note that I am interested in 6 decimal places after that I could ignore the values.

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");
}

or is there a better way.

回答1:

If you are rounding the values to 6 decimal places, then your epsilon value is too small. The smallest amount the two values can differ is 0.000001.

For example:

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");
}


回答2:

The following works for me:

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");
}

The above will display "diff".

If you change to var valueOne = 1.1234565M; the difference will be smaller than threshold, thus it will display "equal".

Then you can Round or Truncate depending on your needs.

EDIT: @tangokhi just noticed your answer! You are correct.. ignore my reply.



回答3:

This answer is based on the top-voted one here: Floating point comparison functions for C#

There are edge cases to be accounted for that preclude direct comparsion, as shown here. Basically because decimals could be equal, but not actually seen as equal by code, i.e.

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!

You have to specify how close you want to compare the numbers. The answer given at the link terms that as an "Epsilon", but they didn't go into detail on if that was placevalues, a range, or simply an increment of the numbers given.

The "Epsilon" in the following function will compare the numbers to within the given degree of difference from each other. Ex., if you wanted to use the above example and make it come back as true, you would want to compare within 0.1 of each other, not 0.01, like it would do by default when comparing 0.30 to 0.3.

    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;
        }
    }

Say you have a Dictionary of items with an item ID and a floating-point decimal (double) number, like a bunch of latitudes...

 Dictionary<int, double> cityLatPoints = new Dictionary<int, double>();

And you want to know if a latitude is near one of those points... here's how you'd do that:

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
    }
}

For the example given, it would look like this:

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");
}


回答4:

fix the typos "var valTwo = decimal.Roung(valueTwo, 6);" it should be decimal.Round(....

You can also compare decimal by using Decimal.Equals(dec1, dec2) or Decimal.Compare(dec1, dec2)



回答5:

you can make a function and do something like this

public static bool Check(decimal first, decimal second, decimal margin) 
{ 
    return Math.Abs(first - second) <= margin; 
}

it will return true or false depending if the values are smaller or equal or not



回答6:

I think if I don't use Round then this solution is fine.

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");
}

As mentioned above for six decimal places the smallest amount the two decimals can differ is 0.000001M. Anything less than that can be safely ignored. I think this solution is fine but if anyone thinks I have missed something I appreciate your help.

Thanks all