Floating Point Comparison Functions For C#

Floating point comparison functions for C#

Writing a useful general-purpose floating point IsEqual is very, very hard, if not outright impossible. Your current code will fail badly for a==0. How the method should behave for such cases is really a matter of definition, and arguably the code would best be tailored for the specific domain use case.

For this kind of thing, you really, really need a good test suite. That's how I did it for The Floating-Point Guide, this is what I came up with in the end (Java code, should be easy enough to translate):

public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);

if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || absA + absB < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}

You can also find the test suite on the site.

Appendix: Same code in c# for doubles (as asked in questions)

public static bool NearlyEqual(double a, double b, double epsilon)
{
const double MinNormal = 2.2250738585072014E-308d;
double absA = Math.Abs(a);
double absB = Math.Abs(b);
double diff = Math.Abs(a - b);

if (a.Equals(b))
{ // shortcut, handles infinities
return true;
}
else if (a == 0 || b == 0 || absA + absB < MinNormal)
{
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * MinNormal);
}
else
{ // use relative error
return diff / (absA + absB) < epsilon;
}
}

Floating point comparison functions for C#

Writing a useful general-purpose floating point IsEqual is very, very hard, if not outright impossible. Your current code will fail badly for a==0. How the method should behave for such cases is really a matter of definition, and arguably the code would best be tailored for the specific domain use case.

For this kind of thing, you really, really need a good test suite. That's how I did it for The Floating-Point Guide, this is what I came up with in the end (Java code, should be easy enough to translate):

public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);

if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || absA + absB < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}

You can also find the test suite on the site.

Appendix: Same code in c# for doubles (as asked in questions)

public static bool NearlyEqual(double a, double b, double epsilon)
{
const double MinNormal = 2.2250738585072014E-308d;
double absA = Math.Abs(a);
double absB = Math.Abs(b);
double diff = Math.Abs(a - b);

if (a.Equals(b))
{ // shortcut, handles infinities
return true;
}
else if (a == 0 || b == 0 || absA + absB < MinNormal)
{
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * MinNormal);
}
else
{ // use relative error
return diff / (absA + absB) < epsilon;
}
}

Comparing floating point values

The following extension methods may be useful to implement Kevin's suggestion:

public static bool IsEqualTo(this double a, double b, double margin)
{
return Math.Abs(a - b) < margin;
}

public static bool IsEqualTo(this double a, double b)
{
return Math.Abs(a - b) < double.Epsilon;
}

So now you can just do:

if(x1.IsEqualTo(x2)) ...
if(x1.IsEqualTo(x2, 0.01)) ...

Just change the IsEqualTo to a more appropriate name, or change the default margin to anything better than double.Epsilon, if needed.

Choosing an Epsilon Value for Floating Point Comparisons

Your reasoning seems sound, but as you have already discovered it is a complicated issue. You might want to read What Every Computer Scientist Should Know About Floating-Point Arithmetic. You do have a minimum of 15 digits of precision using 64 bit doubles. However, you will also want to validate your inputs as floats can contain Nan, +/- Infinity, negative zero and a considerably larger "range" than 15 decimal digits. If someone hands your library a value like 1.2E102, should you process it or consider it out of range? Ditto with very small values. Garbage In, Garbage out, but it might be nice if you code detected the "smell" of garbage and at very least logged it.

You might also want to consider providing a property for setting precision as well as different forms of rounding. That depends largely on the specifications you are working with. You might also want to determine if these values can represent currencies other than dollars (1 dollar is currently >112 yen).

Long and the short of it choosing your epsilon a digit below your needs (so four digits to the right of the decimal) is sound and gives you a digit to use for consistent rounding. Otherwise $10.0129 and $10.0121 would be equal but their sum would be $20.025 rather than $20.024 ... accountants like things that "foot".

Ideal way to test for practical floating point equality in c#

If there was a comparison that met your criteria

  • Works 100% of the time
  • Easy to read/understand
  • High performance

then your favorite programming language would already define comparison operators on floating-point types to do that.

But in reality, the "right" thing to do varies widely based on the meaning of the stored number. There is no one-size-fits-all relaxation of floating point equality!!!

You will have to think about the data you have, its accuracy, measurement error, quantization error, rounding error in calculations, and what decisions you are making with it. Trying to delegate this thinking to someone making general purpose tools is doomed to fail.

Even defining "equal enough" with deep domain knowledge is subject to a lot of pitfalls, for example you may1 find that this happens:

  • Key already exists in unordered_map, but "find" returns as not found

1 By "may" I mean this happens more often than your "ideal" floating-point comparison works.

Is this a valid float comparison that accounts for a set number of decimal places?

Based on @infact's answer and some comments in both the question and the answer I came up with

public static bool AlmostEquals(this float float1, float float2, int precision = 2) 
{
float epsilon = Math.Pow(10.0, -precision)
return (Math.Abs(float1-float2) <= epsilon);
}

This has the benfit of accepting any integer, you could check for > 1.0 precision by using precision = -x, where x is the power of 10 to check against.

I would also recommend making the default precision = 3, which would give you accuracy down to a tenth of a penny, by default, if this method were used for financials.

Can you compare floating point values exactly to zero?

Even though 0 has an exact representation, you can't rely on the result of a calculation using floats to be exactly 0. As you noted, this is due to floating point calculation and conversion issues.

So, you should test for 0 against your tolerance epsilon.

Safe to compare C# doubles against assigned values?

Well, yes and no. If all of the floating-point values you are comparing are stored as constants and the constants are the same data width (i.e., you compare float to float and double to double), this is a safe operation. But if everything you were comparing was a constant, why not use integers or enums?

In general, comparing floating-point numbers for equality is unsafe. The reason for this is that floating-point numbers can't perfectly store all values. This is a lot like the problem with the number 1/3 in decimal which we would have to write as 0.33333... The same problem exists for storing fractional parts in binary, and numbers that have finite representations in decimal notation are not guaranteed to have finite binary representations. Since we are limited to 32 or 64 bits, part of the number gets truncated. This means that performing math operations on floating-point numbers could result in unexpected consequences.

Consider this quote from this post from Bruce M. Bush:

At the heart of many strange results is one fundamental:
floating-point on computers is usually base 2, whereas the external
representation is base 10. We expect that 1/3 will not be exactly
representable, but it seems intuitive that .01 would be. Not so! .01
in IEEE single-precision format is exactly 10737418/1073741824 or
approximately 0.009999999776482582.

You should usually check equality in floating-point values using some small epsilon for variance.

public class Foo
{
//Choose a small value that is appropriate for your needs
//see the info below for some info from Microsoft
private static double epsilon = 0.00001;
private double defaultEfficiency = 100.0;
public double efficiencyBar = defaultEfficiency;

public bool IsBarAtDefaultValue()
{
//we use the absolute value of the difference. If this is smaller than
//epsilon, then the value is "good enough" for equal
if (Math.Abs(efficiencyBar - defaultEfficiency) < epsilon)
return true;
else
return false;
}
}

You could use something like Double.Epsilon for your epsilon value, but this is probably way too small for your needs and is recommended against in the documentation:

If you create a custom algorithm that determines whether two floating-point numbers can be considered equal, we do not recommend that you base your algorithm on the value of the Epsilon constant to establish the acceptable absolute margin of difference for the two values to be considered equal. (Typically, that margin of difference is many times greater than Epsilon.)

And in their documentation on the Double.Equals() method:

Because Epsilon defines the minimum expression of a positive value whose range is near zero, the margin of difference between two similar values must be greater than Epsilon. Typically, it is many times greater than Epsilon. Because of this, we recommend that you do not use Epsilon when comparing Double values for equality.

Both places are good sources of additional information on comparing floating-point numbers safely.



Related Topics



Leave a reply



Submit