6

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.

tangokhi
  • 945
  • 2
  • 8
  • 20
  • 2
    This line `var difference = Math.Abs(valOne, valTwo);` won't compile. Should be `var difference = Math.Abs(valOne - valTwo);`. And `decimal.Roung` should be `decimal.Round` – Tom Chantler Jun 09 '15 at 09:06
  • Sorry, it was a typo but the code above is just for reference. – tangokhi Jun 09 '15 at 09:16
  • This was asked later than this question, but is basically the same & has some good material - https://stackoverflow.com/questions/3874627/floating-point-comparison-functions-for-c-sharp – vapcguy Sep 19 '18 at 13:58
  • Does this answer your question? [How to properly compare decimal values in C#?](https://stackoverflow.com/questions/5940222/how-to-properly-compare-decimal-values-in-c) – Michael Freidgeim Aug 01 '21 at 23:52

7 Answers7

6

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");
}
Dave R.
  • 7,206
  • 3
  • 30
  • 52
  • The above solution will print "Values differ" although the values are same considering that we are only interested in 6 decimal places. so in both numbers above 6 decimal places are:- 123456 only the last digit differ. I couldn't figure out a way to effectively ignore digits after 6th decimal place. – tangokhi Jun 09 '15 at 10:15
  • @tangokhi - If you want to ignore figures past the sixth decimal place, then you should not perform the `Round` function. You can do: `valOne = valOne - (valOne % 0.000001);` to truncate the figures you're not interested in. Alternatively you could multiply by 10^6, then convert to an int to get rid of the extra precision, then divide by 10^6 again. – Dave R. Jun 09 '15 at 11:21
1

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.

karask
  • 736
  • 5
  • 10
1

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");
}
vapcguy
  • 7,097
  • 1
  • 56
  • 52
0

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)

Henry
  • 1,010
  • 8
  • 10
  • Do you think Decimal.Equals or Decimal.Compare would take care of the epsilon value (0.0000001m) and this check is not needed :- if (difference > 0.0000001m) – tangokhi Jun 09 '15 at 09:17
0

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

Blaatz0r
  • 1,205
  • 1
  • 12
  • 24
Bogdan Banciu
  • 169
  • 1
  • 6
0

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

tangokhi
  • 945
  • 2
  • 8
  • 20
  • 6
    In future, I'd suggest accepting Dave's answer, and leaving a comment on it saying your final solution omitted the rounding, rather than posting a new answer. In general, reserve posting answers for when your solution differs wildly to anything else posted. – Matt Jun 24 '15 at 11:46
0

This method is used to compare two specified Decimal values. Syntax: public static int Compare (decimal a1, decimal a2);

Parameters: a1:This parameter specifies the first value to compare. a2:This parameter specifies the second value to compare

tryingToLearn
  • 10,691
  • 12
  • 80
  • 114