62

I come from a background in C++, and I know that you cannot accurately compare floats for equality. For C#, I simply assumed the same policy applies to decimal values, or any floating point value in general.

Basically, I have two decimal values and if they are NOT equal to each other, I need to perform some action. e.g.:

decimal value1, value2;
// Assume value1 and value2 are set somewhere to valid values.
if( value1 != value2 )
{
    // Do something
}

If this doesn't work as expected, I'm willing to accept a solution that does an equality comparison with a margin of error, say like .00001 or something like that. What would be the recommended solution to this problem?

void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • It's not exactly the same as it relates to double rather than decimal but take a look at http://stackoverflow.com/questions/485175/c-net-is-it-safe-to-check-floating-point-values-for-equality-to-0/485210#485210 – Stu Mackellar May 09 '11 at 17:16
  • @Mark: Good point. Withdrawn. – Bob Kaufman May 09 '11 at 17:18
  • 12
    Your posted code is the correct way to compare two decimals. If you want to compare two decimals with a margin of error, you would just do `if (Math.Abs(value2 - value1) < 0.00001) { ... }`. – MusiGenesis May 09 '11 at 17:18
  • When you say "decimal", are you talking about [the `decimal` type useful for financial and monetary calculations](http://msdn.microsoft.com/en-us/library/364x0z75(v=VS.100).aspx) or are you using "decimal" as an umbrella term for a variety of floating-point formats including [`double`](http://msdn.microsoft.com/en-us/library/678hzkk9.aspx), [`float`](http://msdn.microsoft.com/en-us/library/b1e65aza.aspx), and `decimal` (as linked above)? – Matt Ball May 09 '11 at 17:27
  • I want to clarify my usage of 'decimal' in my original post. Basically, I'm referring to the actual type 'decimal' (the actual keyword). I don't really understand what the underlying data looks like for decimal types, I simply assumed they were floating point values, where for fractional types they are never exactly the same (like with floats & doubles in C++). – void.pointer May 09 '11 at 18:16

4 Answers4

37

Your code will work as expected. C# decimals are optimized to be very accurate at representing base 10 numbers, so if that's what you're comparing (money, ...), everything should be fine.

Here's a very clear explanation about the accuracy of decimals by Jon Skeet:

Difference between decimal, float and double in .NET?

Community
  • 1
  • 1
fretje
  • 8,322
  • 2
  • 49
  • 61
  • All of the answers provided are helplful. I'm marking this one as my answer mostly because I realized I didn't fully understand what a decimal was in C#. Decimals don't appear to be floats as I thought they were. Also the link to the article explaining the differences is very very helpful for me. – void.pointer May 09 '11 at 18:19
  • 8
    @Robert: decimals are floats in the sense that the position of the decimal point "floats around". Decimals are not "fixed point" numbers where there are, say, 15 places before the decimal point and 10 after. The difference between decimal and double is that a decimal is a number of the form m x 10^e, and a double is a number of the form m x 2^e. – Eric Lippert May 09 '11 at 18:24
  • 4
    Just saw that `0.0000 != 0.0` returns true. However `!(0.000).Equals(0.0)` returns false. – Michał Woliński May 28 '19 at 08:24
  • I have for example TotalPrice = 10, Quantity = 3, and UnitPrice is computed as UnitPrice = TotaPrice / Quantity. The user can enter any 2 of the 3 values. How do I check if TotalPrice is correct? – Jeno Csupor May 14 '21 at 11:58
9

I was investigating something similar, but with a precision instead of a margin of error and ended up writing some extensions for Float. This can easily be adapted for any type though. I've got a complicated series of comparisons and this makes it nice and readable.

/// <summary>
/// A set of extensions to allow the convenient comparison of float values based on a given precision.
/// </summary>
public static class FloatingPointExtensions
{
    /// <summary>
    /// Determines if the float value is less than or equal to the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool LessThan(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) < 0);
    }

    /// <summary>
    /// Determines if the float value is less than or equal to the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool LessThanOrEqualTo(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) <= 0);
    }

    /// <summary>
    /// Determines if the float value is greater than (>) the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool GreaterThan(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) > 0);
    }

    /// <summary>
    /// Determines if the float value is greater than or equal to (>=) the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool GreaterThanOrEqualTo(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) >= 0);
    }

    /// <summary>
    /// Determines if the float value is equal to (==) the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool AlmostEquals(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) == 0);
    } 
}
Kim
  • 1,068
  • 13
  • 25
6

I agree with the other answers, but I run into a problem where one "authentic" server-side decimal gets compared with one coming from JSON/browser (and must have been a float at some point).

I ended up with this code to round to 2 digits after the decimal point, which was precise enough in my case:

if (Decimal.Round(serverTotalPrice, 2) != Decimal.Round(request.TotalPrice, 2)) {
    throw new ArgumentException("The submitted Total Price is not valid");
}
Ekus
  • 1,679
  • 21
  • 17
  • 3
    using round for such task may cause issues, because 1.116 would be rounded to 1.12 and 1.113 would be rounded to 1.11, thus they would be not equal according to your code, where it seems you want them to consider equal. – Igor Yalovoy Apr 27 '17 at 10:12
  • @iYalovoi true, I thought about it myself but in my case the floating point version in js is based on fixed, decimal value in server-side .net, and so my server-side 1.12 would be in js either something like 1.11999999 or 1.12000001 which still works OK for me. – Ekus Apr 27 '17 at 16:48
5

I think this will solve your problem.

Basically there is a decimal.compare method.

EDIT: This may be the better method:

Decimal.Equals

EDIT2: If you can compare directly as suggested above, that may be more efficient. I will leave this as it may be of interest.

nycdan
  • 2,819
  • 2
  • 21
  • 33
  • You may be right. There is a decimal.equals in there as well. I haven't used them that I recall but it seems to be appropriate. If anyone knows differently, I'd be curious as well. – nycdan May 09 '11 at 17:24
  • This has the same issue as `x == y`, where x is a floating point kind. (The fact that decimals have a fixed precision is only somewhat relevant, as it is the math that gets there that is problematic.) – user2864740 Apr 13 '18 at 19:30