Assert.AreEqual() with System.Double getting really confusing

23,929

Solution 1

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.

Solution 2

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

Solution 3

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;

Solution 4

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.

Solution 5

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

Share:
23,929
dknaack
Author by

dknaack

Updated on January 05, 2020

Comments

  • dknaack
    dknaack over 4 years

    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?