1

I have a simple example application here where I am multiplying and adding double variables and then comparing them against an expected result. In both cases the result is equal to the expected result yet when I do the comparison it fails.

static void Main(string[] args)
{
    double a = 98.1;
    double b = 107.7;
    double c = 92.5;
    double d = 96.5;

    double expectedResult = 88.5;
    double result1 = (1*2*a) + (-1*1*b);
    double result2 = (1*2*c) + (-1*1*d);            

    Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", a, b, result1, expectedResult == result1));
    Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", c, d, result2, expectedResult == result2));

    Console.Read();
}

And here is the output:

2x98.1 - 107.7 = 88.5
Equal to 88.5? False

2x92.5 - 96.5 = 88.5
Equal to 88.5? True

I need to be able to capture that it is in fact True in BOTH cases. How would I do it?

Michael Mankus
  • 4,628
  • 9
  • 37
  • 63

7 Answers7

4

Floating point numbers often don't contain the exact value that mathematics tells us, because of how they store numbers.

To still have a reliable comparison, you need to allow some difference:

private const double DoubleEpsilon = 2.22044604925031E-16;

/// <summary>Determines whether <paramref name="value1"/> is very close to <paramref name="value2"/>.</summary>
/// <param name="value1">The value1.</param>
/// <param name="value2">The value2.</param>
/// <returns><c>true</c> if <paramref name="value1"/> is very close to value2; otherwise, <c>false</c>.</returns>
public static bool IsVeryCloseTo(this double value1, double value2)
{
    if (value1 == value2)
        return true;

    var tolerance = (Math.Abs(value1) + Math.Abs(value2)) * DoubleEpsilon;
    var difference = value1 - value2;

    return -tolerance < difference && tolerance > difference;
}

Please also make sure to read this blog post.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • +1 But can I ask you where did you take that formula from? Silverlight Control Toolkit? Or is there some real math behind the voodoo? Because... http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre – xanatos Aug 06 '13 at 13:22
  • @xanatos: Good question. It is from one of my helper libraries I haven't changed in years. I am pretty sure I got it from the net somewhere. I am trying to track down the source. – Daniel Hilgarth Aug 06 '13 at 13:31
  • @xanatos: About the constant `DoubleEpsilon`, see http://en.wikipedia.org/wiki/Machine_epsilon. Still not sure about the rest. – Daniel Hilgarth Aug 06 '13 at 13:44
  • 1
    Your constant is `Math.Pow(2, -52)` rounded to 15 significant decimal figures. ***EDIT:*** To get the exact number, two to the minus 52nd, insert an extra digit `3` just before the `E` in your `const` declaration. – Jeppe Stig Nielsen Aug 06 '13 at 13:45
  • @JeppeStigNielsen: Correct. – Daniel Hilgarth Aug 06 '13 at 13:47
  • To elaborate further, the binary representation of your `const` above, as revealed by `BitConverter.DoubleToInt64Bits(DoubleEpsilon).ToString("X8")` is `0x3CAFFFFFFFFFFFF4`. It is evident that you don't quite "hit" the exact round number. The real "two to the minus 52nd" is twelve "steps" higher, represented by `0x3CB0000000000000`. – Jeppe Stig Nielsen Aug 06 '13 at 14:08
  • @JeppeStigNielsen: Which is attributed to the missing `3` you mention, I guess. However, I would be more interested in the rest of the logic. Do you have an explanation for that, too? I assume that the + 10 is there to bring the value into an area where the epsilon makes sense, but in that case `+ 1.0` should have done the same job... – Daniel Hilgarth Aug 06 '13 at 14:12
  • Yes, the missing `3`. // Possibly someone put the `10.0` there to cope with special cases where `value1` or `value2` are zero? It looks like that term `10.0` kind of spoils the method. For example the method claims `2.3E-120` and `8.7E-193` are close, but they are not close at all! The `num1` could be renamed to `tolerance`, and the `num2` could be renamed `difference`, for clarity. Then the last part of the method could be rewritten into `return -tolerance < difference && difference < tolerance;`. – Jeppe Stig Nielsen Aug 06 '13 at 14:44
  • Also, for values well over 10, where this term doesn't really matter, the above method is quite strict (which might be intentional). For example if `value1 = 1000.0000000000038` and `value2 = 1000.0000000000042`, then both of these values will print out as just `"1000"`. Even if we force a sixteenth digit to appear with `.ToString("G16")` they still print out the same (this time as `"1000.000000000004"`). Not until we use `"G17"` or `"R"` format string, can we see their difference. But `value1.IsVeryCloseTo(value2)` is still false for this distance. – Jeppe Stig Nielsen Aug 06 '13 at 15:21
  • 1
    @JeppeStigNielsen: This is a really interesting topic. Unfortunately, I was unable to find the original source and I can't find any other source that justifies the `+ 10`. Because of this, I adjusted the code in the answer. – Daniel Hilgarth Aug 06 '13 at 15:34
  • @JeppeStigNielsen: About the strictness: The strictness should be the same for the whole number range when seen in relation to the actual numbers being compared. – Daniel Hilgarth Aug 06 '13 at 15:37
1

If you need more precision (for money and such) then use decimal.

var a = 98.1M;
var b = 107.7M;
var c = 92.5M;
var d = 96.5M;

var expectedResult = 88.5M;
var result1 = (2 * a) + (-1 * b);
var result2 = (2 * c) + (-1 * d);

Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", a, b, result1, expectedResult == result1));
Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", c, d, result2, expectedResult == result2));

Output:

2x98.1 - 107.7 = 88.5
Equal to 88.5? True

2x92.5 - 96.5 = 88.5
Equal to 88.5? True
Dustin Kingen
  • 20,677
  • 7
  • 52
  • 92
  • `decimal` is *NOT* fixed point. It still is floating point. But the internal representation is not binary but decimal, that's why it works well with "normal" numbers like 0.1. Simple proof: `Assert.Equal(1/3m * 2, 2/3m); // will fail` – Daniel Hilgarth Aug 06 '13 at 13:32
  • @DanielHilgarth I can agree that `decimal` is not fixed-point. But the proof is not valid, because for a fixed-point type that assert would fail as well. For example with three fixed decimals after the point, one value would be `0.666` while the other would be `0.667`. (I was assuming a fixed-point arithmetic where division rounded to nearest representable value, not just truncated.) – Jeppe Stig Nielsen Aug 06 '13 at 14:17
0

It's a problem with how floating point numbers are represented in memory.

You should read this to get a better understanding of whats going on: What Every Computer Scientist Should Know About Floating-Point Arithmetic

Artless
  • 4,522
  • 1
  • 25
  • 40
0

Simply change your rounding to level 2 , this will give TRUE

double result1 =Math.Round ( (1 * 2 * a) + (-1 * 1 * b),2);
faheem khan
  • 471
  • 1
  • 7
  • 33
0

using Math.Round() will round result1 to the correct decimal

result1 = Math.Round(result1, 1);
Jonesopolis
  • 25,034
  • 12
  • 68
  • 112
0

using the debugger,

result1=88.499999999999986;
expectedResult = 88.5

So when using the double ,these are not equal.

Naren
  • 2,231
  • 1
  • 25
  • 45
0

There is a whole school of thought that is against using Double.Epsilon and similar numbers...

I think they use this: (taken from https://stackoverflow.com/a/2411661/613130 but modified with the checks for IsNaN and IsInfinity suggested here by nobugz

public static bool AboutEqual(double x, double y)
{
    if (double.IsNaN(x)) return double.IsNaN(y); 
    if (double.IsInfinity(x)) return double.IsInfinity(y) && Math.Sign(x) == Math.Sign(y);

    double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
    return Math.Abs(x - y) <= epsilon;
}

The 1E-15 "magic number" is based on the fact that doubles have a little more than 15 digits of precision.

I'll add that for your numbers it returns true :-)

Community
  • 1
  • 1
xanatos
  • 109,618
  • 12
  • 197
  • 280