How do I find if two variables are approximately equals?
Asked Answered
W

7

46

I am writing unit tests that verify calculations in a database and there is a lot of rounding and truncating and stuff that mean that sometimes figures are slightly off.

When verifying, I'm finding a lot of times when things will pass but say they fail - for instance, the figure will be 1 and I'm getting 0.999999

I mean, I could just round everything into an integer but since I'm using a lot of randomized samples, eventually i'm going to get something like this

10.5 10.4999999999

one is going to round to 10, the other will round to 11.

How should I solve this problem where I need something to be approximately correct?

Whipstall answered 6/8, 2010 at 3:21 Comment(2)
Comparing Floating Point Numbers, 2012 Edition: randomascii.wordpress.com/2012/02/25/… - not a C# version be you will get everything you needDeirdredeism
Math.Round() has an overload available since Framework 1.1 that allows you to specify the number of digits and, since 2.0, you can specify how to handle midpoint rounding.Swayback
D
87

Define a tolerance value (aka an 'epsilon' or 'delta'), for instance, 0.00001, and then use to compare the difference like so:

if (Math.Abs(a - b) < delta)
{
   // Values are within specified tolerance of each other....
}

You could use Double.Epsilon but you would have to use a multiplying factor.

Better still, write an extension method to do the same. We have something like Assert.AreSimiliar(a,b) in our unit tests.

Microsoft's Assert.AreEqual() method has an overload that takes a delta: public static void AreEqual(double expected, double actual, double delta)

NUnit also provides an overload to their Assert.AreEqual() method that allows for a delta to be provided.

Dallman answered 6/8, 2010 at 3:23 Comment(0)
C
30

You could provide a function that includes a parameter for an acceptable difference between two values. For example

// close is good for horseshoes, hand grenades, nuclear weapons, and doubles
static bool CloseEnoughForMe(double value1, double value2, double acceptableDifference)
{
    return Math.Abs(value1 - value2) <= acceptableDifference; 
}

And then call it

double value1 = 24.5;
double value2 = 24.4999;

bool equalValues = CloseEnoughForMe(value1, value2, 0.001);

If you wanted to be slightly professional about it, you could call the function ApproximatelyEquals or something along those lines.

static bool ApproximatelyEquals(this double value1, double value2, double acceptableDifference)
Clavius answered 6/8, 2010 at 3:28 Comment(2)
+1 for a correct answer, +1 for suggesting an extension, -1 for not being snobby enough to use a Greek letter. :-)Shabby
I'd call it CloseEnoughForGovernmentWork(), personallyGubernatorial
B
12

I haven't checked in which MS Test version were added but in v10.0.0.0 Assert.AreEqual methods have overloads what accept a delta parameter and do approximate comparison.

I.e. https://msdn.microsoft.com/en-us/library/ms243458(v=vs.140).aspx

//
// Summary:
//     Verifies that two specified doubles are equal, or within the specified accuracy
//     of each other. The assertion fails if they are not within the specified accuracy
//     of each other.
//
// Parameters:
//   expected:
//     The first double to compare. This is the double the unit test expects.
//
//   actual:
//     The second double to compare. This is the double the unit test produced.
//
//   delta:
//     The required accuracy. The assertion will fail only if expected is different
//     from actual by more than delta.
//
// Exceptions:
//   Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException:
//     expected is different from actual by more than delta.
public static void AreEqual(double expected, double actual, double delta);
Bract answered 10/7, 2014 at 8:41 Comment(0)
L
5

In NUnit, I like the clarity of this form:

double expected = 10.5;
double actual = 10.499999999;
double tolerance = 0.001;
Assert.That(actual, Is.EqualTo(expected).Within(tolerance));
Limonite answered 29/1, 2021 at 0:31 Comment(0)
B
2

One way to compare floating point numbers is to compare how many floating point representations that separate them. This solution is indifferent to the size of the numbers and thus you don't have to worry about the size of "epsilon" mentioned in other answers.

A description of the algorithm can be found here (the AlmostEqual2sComplement function in the end) and here is my C# version of it.

UPDATE: The provided link is outdated. The new version which includes some improvements and bugfixes is here

public static class DoubleComparerExtensions
{
    public static bool AlmostEquals(this double left, double right, long representationTolerance)
    {
        long leftAsBits = left.ToBits2Complement();
        long rightAsBits = right.ToBits2Complement();
        long floatingPointRepresentationsDiff = Math.Abs(leftAsBits - rightAsBits);
        return (floatingPointRepresentationsDiff <= representationTolerance);
    }

    private static unsafe long ToBits2Complement(this double value)
    {
        double* valueAsDoublePtr = &value;
        long* valueAsLongPtr = (long*)valueAsDoublePtr;
        long valueAsLong = *valueAsLongPtr;
        return valueAsLong < 0
            ? (long)(0x8000000000000000 - (ulong)valueAsLong)
            : valueAsLong;
    }
}

If you'd like to compare floats, change all double to float, all long to int and 0x8000000000000000 to 0x80000000.

With the representationTolerance parameter you can specify how big an error is tolerated. A higher value means a larger error is accepted. I normally use the value 10 as default.

Bannister answered 3/5, 2012 at 5:43 Comment(5)
How about comparing zero and very small negative value. Seems that it does not work. e.g. compare 0 and -1.1102230246251565E-16Deirdredeism
Provided link is outdated. New location is randomascii.wordpress.com/2012/02/25/…Deirdredeism
I don't understand why you subtract negative values from 0x800... Could you please elaborate on this?Insane
@m93a From the original article: "A more general way of handling negative numbers is to adjust them so that they are lexicographically ordered as twos-complement integers instead of as signed magnitude integers. This is done by detecting negative numbers and subtracting them from 0x80000000. This maps negative zero to an integer zero representation and it makes it so that the smallest negative number is represented by negative one, and downwards from there."Rhetorician
Math.Abs will throw OverflowException if we try to call AlmostEquals(4, -1). Does that mean we are better off doing this: long floatingPointRepresentationsDiff = leftAsBits - rightAsBits; return (floatingPointRepresentationsDiff <= representationTolerance && floatingPointRepresentationsDiff >= -representationTolerance);Moline
C
1

The question was asking how to assert something was almost equal in unit testing. You assert something is almost equal by using the built-in Assert.AreEqual function. For example:

Assert.AreEqual(expected: 3.5, actual : 3.4999999, delta:0.1);

This test will pass. Problem solved and without having to write your own function!

Clownery answered 25/10, 2017 at 17:11 Comment(1)
Provide your own answers, don't copy others. Especially if the other is on the same page :-pBract
M
0

FluentAssertions provides this functionality in a way that is perhaps clearer to the reader.

result.Should().BeApproximately(expectedResult, 0.01m);

Macymad answered 20/1, 2023 at 10:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.