29

Description

This is not a real world example! Please don't suggest using decimal or something else.

I am only asking this because I really want to know why this happens.

I recently saw the awesome Tekpub Webcast Mastering C# 4.0 with Jon Skeet again.

On episode 7 - Decimals and Floating Points it is going really weird and even our Chuck Norris of Programming (aka Jon Skeet) does not have a real answer to my question. Only a might be.

Question: Why did MyTestMethod() fail and MyTestMethod2() pass?

Example 1

[Test]
public void MyTestMethod()
{
    double d = 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;

    Console.WriteLine("d = " + d);
    Assert.AreEqual(d, 1.0d);
}
This results in

d = 1

Expected: 0.99999999999999989d But was: 1.0d

Example 2

[Test]
public void MyTestMethod2()
{
    double d = 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;

    Console.WriteLine("d = " + d);
    Assert.AreEqual(d, 0.5d);
}
This results in success

d = 0,5

But why ?

Update

Why doesn't Assert.AreEqual() cover that?

Sergey
  • 1,608
  • 1
  • 27
  • 40
dknaack
  • 60,192
  • 27
  • 155
  • 202
  • 7
    You should read [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). – Oded Jan 11 '12 at 20:16
  • 2
    Simply luck. It looks like the second one rounded in your favor, the first didn't. You have undefined behavior, so don't expect any logical explanation. – CodesInChaos Jan 11 '12 at 20:17
  • @Oded i know that, but why `Assert.AreEqual()` does not cover that ? – dknaack Jan 11 '12 at 20:21
  • 4
    @dknaack - How could it? The overloads that take 2 parameters are the generic one and the one taking 2 objects. You can use one of the overloads that _specifically_ takes `doubles` and that also takes the error delta. – Oded Jan 11 '12 at 20:24

7 Answers7

82

Assert.AreEqual() does cover that; you have to use the overload with a third delta argument:

Assert.AreEqual(0.1 + 0.1 + 0.1, 0.3, 0.00000001);
phoog
  • 42,068
  • 6
  • 79
  • 117
  • what about collectionsAssert.AreEqual()? – cikatomo Oct 04 '21 at 21:30
  • @cikatomo what about it? There are no collections in the question. – phoog Oct 04 '21 at 21:44
  • I am only asking if you know, because I need the same but for collection – cikatomo Oct 05 '21 at 01:03
  • 1
    @cikatomo I see. In that case, you can [ask a new question](https://stackoverflow.com/questions/ask). – phoog Oct 05 '21 at 06:21
  • I got a NUnit 2005 when I used the above, and the recommended change by VS was to use the constraint model. Adding it on here so that anyone else can also refer to it: `Assert.That(actual, Is.EqualTo(expected).Within(0.00000001));` – Tan Yu Hau Sean Jan 03 '23 at 03:27
16

Because Doubles, like all floating point numbers, are approximations, not absolute values binary (base-2) representations, which may not be able to perfectly represent base-10 fractions (the same way that base-10 cannot represent 1/3 perfectly). So the fact that the second one happens to round to the correct value when you perform equality comparison (and the fact that the first one doesn't) is just luck, and not a bug in the framework or anything else.

Also, read this: Casting a result to float in method returning float changes result

Assert.Equals does not cover this case because the principle of least astonishment states that since every other built-in numeric value type in .NET defines .Equals() to perform an equivalent operation of ==, so Double does so as well. Since in fact the two numbers that you are generating in your test (the literal 0.5d and the 5x sum of .1d) are not == equal (the actual values in the processors' registers are different) Equals() returns false.

It is not the framework's intent to break the generally accepted rules of computing in order to make your life convenient.

Finally, I'd offer that NUnit has indeed realized this problem and according to http://www.nunit.org/index.php?p=equalConstraint&r=2.5 offers the following method to test floating point equality within a tolerance:

Assert.That( 5.0, Is.EqualTo( 5 );
Assert.That( 5.5, Is.EqualTo( 5 ).Within(0.075);
Assert.That( 5.5, Is.EqualTo( 5 ).Within(1.5).Percent;
Community
  • 1
  • 1
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • Yes, i know that, but why Assert.AreEqual() does not cover that ? – dknaack Jan 11 '12 at 20:25
  • 1
    Because the IEEE standard definition for floating point arithmetic (http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=4610935) does not say that it should. – Chris Shain Jan 11 '12 at 20:30
  • So you agree?, really confusing. But of course there must be an answer. – dknaack Jan 11 '12 at 20:39
  • @ChrisShain MSTest also realizes this; the `double` overload of the `AreEqual` method has a third "delta" argument. The "Assert.Equals" method in MSTest is not the method in question; that's just the overload inherited from `object`. Its documentation says "To compare two instances of an object, use the AreEqual method." – phoog Jan 11 '12 at 21:55
11

Okay, I haven't checked what Assert.AreEqual does... but I suspect that by default it's not applying any tolerance. I wouldn't expect it to behind my back. So let's look for another explanation...

You're basically seeing a coincidence - the answer after four additions happens to be the exact value, probably because the lowest bit gets lost somewhere when the magnitude changes - I haven't looked at the bit patterns involved, but if you use DoubleConverter.ToExactString (my own code) you can see exactly what the value is at any point:

using System;

public class Test
{    
    public static void Main()
    {
        double d = 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;        
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
    }
}

Results (on my box):

d = 0.1000000000000000055511151231257827021181583404541015625
d = 0.200000000000000011102230246251565404236316680908203125
d = 0.3000000000000000444089209850062616169452667236328125
d = 0.40000000000000002220446049250313080847263336181640625
d = 0.5

Now if you start with a different number, it doesn't work itself out in the same way:

(Starting with d=10.1)

d = 10.0999999999999996447286321199499070644378662109375
d = 10.199999999999999289457264239899814128875732421875
d = 10.2999999999999989341858963598497211933135986328125
d = 10.39999999999999857891452847979962825775146484375
d = 10.4999999999999982236431605997495353221893310546875

So basically you happened to get lucky or unlucky with your test - the errors cancelled themselves out.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I know about your `DoubleConverter` class. You show them in the Webcast. But it was hard to understand, because the AssertAreEqual() mistake.Now its clear... Damn so many right answers... – dknaack Jan 13 '12 at 22:44
  • 4
    Rather than converting to a string, you really ought to be using the Assert.AreEqual(double, double, double) overload, where the last double is a delta value indicating the accuracy of the comparison, as mentioned below, as well as here: http://msdn.microsoft.com/en-us/library/ms243458.aspx – Joel B Oct 03 '13 at 16:29
  • 1
    @JoelB: The string conversion here is only to show what the exact value was. – Jon Skeet Oct 03 '13 at 16:31
  • Is '(Starting with d=0.1)' suppose to be '(Starting with d=10)' ? – Roman Gudkov Jul 19 '16 at 15:28
  • @RGudkov: yup, fixed. (Well, 10.1) – Jon Skeet Jul 19 '16 at 15:29
9

Assert.AreEqual does take that into account.

But in order to do so, you need to supply your margin of error - the delta within the difference between the two float values are deemed equal for your application.

There are two overloads to Assert.AreEqual that take only two parameters - a generic one (T, T) and a non generic one - (object, object). These can only do the default comparisons.

Use one of the overloads that take double and that also has a parameter for the delta.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
4

This is the feature of computer floating point arithmetics (http://www.eskimo.com/~scs/cclass/progintro/sx5.html)

It's important to remember that the precision of floating-point numbers is usually limited, and this can lead to surprising results. The result of a division like 1/3 cannot be represented exactly (it's an infinitely repeating fraction, 0.333333...), so the computation (1 / 3) x 3 tends to yield a result like 0.999999... instead of 1.0. Furthermore, in base 2, the fraction 1/10, or 0.1 in decimal, is also an infinitely repeating fraction, and cannot be represented exactly, either, so (1 / 10) x 10 may also yield 0.999999.... For these reasons and others, floating-point calculations are rarely exact. When working with computer floating point, you have to be careful not to compare two numbers for exact equality, and you have to ensure that ``round off error'' doesn't accumulate until it seriously degrades the results of your calculations.

You should explicit set the precision for Assert

For example:

double precision = 1e-6;
Assert.AreEqual(d, 1.0, precision);

It's work for you sample. I often use this way in my code, but precision depending on the situation

Alexander
  • 455
  • 6
  • 11
1

This is because floating point numbers lose precision. The best way to compare equals is to subtract the numbers and verify the different is less then a certain number such as .001 (or to whatever precision you need). Look at http://msdn.microsoft.com/en-us/library/system.double%28v=VS.95%29.aspx specifically the Floating-Point Values and Loss of Precision section.

Jamie Treworgy
  • 23,934
  • 8
  • 76
  • 119
Brad Semrad
  • 1,501
  • 1
  • 11
  • 19
  • i know that, but why Assert.AreEqual() does not cover that ? – dknaack Jan 11 '12 at 20:24
  • @dknaack - You may want to clarify your question then... since that's not what you asked... – Oded Jan 11 '12 at 20:25
  • Precision varies based on each application. I guessing it just calls equals and tries to compare. So you application might require 3 digits but mine requires 6. Equal could not handle that. (Not 100% sure either but guessing) – Brad Semrad Jan 11 '12 at 20:25
0

0.1 can't be represented exactly in a double because of it's internal format.

Use decimal if you want to represent base 10 numbers.

If you want to compare doubles check whether they are within a very small amount of each other.

George Duckett
  • 31,770
  • 9
  • 95
  • 162
  • I'm pretty sure the only difference between `decimal` and `double` is the precision (96 vs 64 bits). `decimal` would likely have the same problem since it still is a floating-point type data structure. If you need to compare decimal values of a known number of digits exactly, then multiply everything by a multiple of 10 until they're all integers first. – Jamie Treworgy Jan 11 '12 at 20:21
  • 4
    That's not correct, decimal is built to store base 10 numbers exactly (up to a point obviously). – George Duckett Jan 11 '12 at 20:22
  • 1
    @jamietre `decimal` is a base-10 floating point data structure, so values like 0.1 and 0.2 have an exact representation as `decimal` values. `double` is a base-2 floating point system, and 0.1 and 0.2 repeat infinitely in that system, so they cannot be represented exactly as `double` values. – phoog Jan 11 '12 at 20:31