Introduction
My introduction is as often: why to bother at all?
The C# double
value type is known to everybody.
Do we know that type really? What is the result of this?
double a = 1.0 / 6.0;
Console.WriteLine(a + a + a + a + a + a == 1.0);
Answer:
False
(adding six sixth does not result in 1.0)!
Or what do we get for this?
double a 1E300 / 1E-100;
Console.WriteLine(a - a == 0.0);
Answer:
False
(
a
is +∞, and
∞ - ∞
is
NaN
, which is not
0.0
)!
Or finally, what do we get with the following:
Random r = new Random();
foreach (var n in Enumerable.Range(0, 10))
{
double sum = 0.0;
foreach (int count in Enumerable.Range(0, 100))
{
sum += Math.Round(r.NextDouble() / 50.0, 2);
}
Console.WriteLine(sum);
}
Answer: you get random sums, of course, but not always to two decimal digits only (rounding is not "exact", since the results is still a double value...)!
This article details on how to deal with issues in comparing double
values. The following items are discussed:
- double type at a glance
- double Equality
- Quirks of double Equality
- Make double Equality tolerant
- double Relation operations
- Quirks of double Relations
- Make double Relations tolerant
- Summary
double type at a glance
The C# double
type is defined according to IEEE-754 specification.
That means that double
:
- is a floating point type
- has a range from about -10308 to 10308
- has a precision of about 15 decimal digits
- has a smallest number (closest to 0.0) of about +/- 10-308
- has two zero values: +/- 0.0
- has two infinty values: +/- ∞
- has a NaN "value" (Not a Number)
- has the commonly known arithmetic operations
- has the commonly known equality and relation operations
- has Math library support for further functions like absolute value, rounding, etc.
double Equality
As we've seen in the intro and in the overview, double
has some special values:
Let's look at the equality table for all these values (plus 1.0
representing a "normal" double
value):
Equality (x==y ) | y=1.0 | y=-0.0 | y=+0.0 | y=-∞ | y=+∞ | y=NaN |
x=1.0 | True | False | False | False | False | False |
x=-0.0 | False | True | True | False | False | False |
x=+0.0 | False | True | True | False | False | False |
x=-∞ | False | False | False | True | False | False |
x=+∞ | False | False | False | False | True | False |
x=NaN | False | False | False | False | False | False |
Inequality (x!=y ) | y=1.0 | y=-0.0 | y=+0.0 | y=-∞ | y=+∞ | y=NaN |
x=1.0 | False | True | True | True | True | True |
x=-0.0 | True | False | False | True | True | True |
x=+0.0 | True | False | False | True | True | True |
x=-∞ | True | True | True | False | True | True |
x=+∞ | True | True | True | True | False | True |
x=NaN | True | True | True | True | True | True |
Notes:
- The surprising equality is given for
NaN
: two NaN
values are not identical!
+0.0
and -0.0
are identical - luckily
Bonus
What to do if you want to know if a value is NaN
?
The equality operator returns always False
for a == double.NaN
...
Answer: use the specific function double.IsNaN(a)
. Likewise, it's prudent to check for infinity by the specific fucntions double.IsPositiveInfinity(a)
and double.IsNegativeInfinity(a)
respectively.
Quirks of double Equality
The nature of floating point arithmetic is that they do implicit rounding.
Not all values can be stored with unlimited precision, leaving an approximation of these values only.
So, this approximation may result in a tiny "rounding error".
Adding up values with rounding errors may result in even larger errors.
double a = 1.0/6.0; Console.WriteLine("equal = {0}", a+a+a+a+a+a == 1.0); Console.WriteLine("delta = {0}", a+a+a+a+a+a - 1.0);
This means, you cannot trust the equality operators ==
and !=
on double
values. As a conclusion, we need an equality comparison that allows for "tiny" delta.
Note: As shown in the introduction, rounding does not help in this. A reliable comparison is needed.
Make double Equality tolerant
To gain trust into the equality operator again, we must provide an equality function that allows for tolerant comparison. For that, we must make sure that the equality/inequality table is met.
A possible solution:
public static class RealExtensions
{
public struct Epsilon
{
public Epsilon(double value) { _value = value; }
private double _value;
internal bool IsEqual (double a, double b) { return (a == b) || (Math.Abs(a - b) < _value); }
internal bool IsNotEqual(double a, double b) { return (a != b) && !(Math.Abs(a - b) < _value); }
}
public static bool EQ(this double a, double b, Epsilon e) { return e.IsEqual (a, b); }
public static bool NE(this double a, double b, Epsilon e) { return e.IsNotEqual(a, b); }
}
Usage:
var epsilon = new RealExtension.Epsilon(1E-3);
...
double x = 1.0 / 6.0;
double y = x+x+x+x+x+x;
if (y.EQ(1.0, epsilon)) ...
Notes:
- These equality/inequality functions work also for the special values.
- Choose the Epsilon value carefully: e.g. angle epsilon are usually very different to length epsilons.
- Epsilon values can be expected to be in the range 1E-1 to 1E-15 (a meaningful limit is 1E-15 since the double prercision is about 15 decimal digits).
- The
double.Epsilon
is not useful for epsilon - it is the smallest possible only.
That's all! Well, not yet...
What about the relation operators: <
, <=
, >=
, >
. They also involve equality and inequality operations.
double Relation operations
First, let's list again the tables of the operations:
Less-than (x<y ) | y=1.0 | y=-0.0 | y=+0.0 | y=-∞ | y=+∞ | y=NaN |
x=1.0 | False | False | False | False | True | False |
x=-0.0 | True | False | False | False | True | False |
x=+0.0 | True | False | False | False | True | False |
x=-∞ | True | True | True | False | True | False |
x=+∞ | False | False | False | False | False | False |
x=NaN | False | False | False | False | False | False |
Less-equal (x<=y ) | y=1.0 | y=-0.0 | y=+0.0 | y=-∞ | y=+∞ | y=NaN |
x=1.0 | True | False | False | False | True | False |
x=-0.0 | True | True | True | False | True | False |
x=+0.0 | True | True | True | False | True | False |
x=-∞ | True | True | True | True | True | False |
x=+∞ | False | False | False | False | True | False |
x=NaN | False | False | False | False | False | False |
Greater-equal (x>=y ) | y=1.0 | y=-0.0 | y=+0.0 | y=-∞ | y=+∞ | y=NaN |
x=1.0 | True | True | True | True | False | False |
x=-0.0 | False | True | True | True | False | False |
x=+0.0 | False | True | True | True | False | False |
x=-∞ | False | False | False | True | False | False |
x=+∞ | True | True | True | True | True | False |
x=NaN | False | False | False | False | False | False |
Greater-than (x>y ) | y=1.0 | y=-0.0 | y=+0.0 | y=-∞ | y=+∞ | y=NaN |
x=1.0 | False | True | True | True | False | False |
x=-0.0 | False | False | False | True | False | False |
x=+0.0 | False | False | False | True | False | False |
x=-∞ | False | False | False | False | False | False |
x=+∞ | True | True | True | True | False | False |
x=NaN | False | False | False | False | False | False |
Notes:
double.NaN
results always in False (i.e. a < b
is not the opposite of a >= b
).
+0.0
and -0.0
are identical again.
What is the issue with the equality/inequality functions is similar with the relations. Let's look into that in the following section.
Quirks of double Relations
Since one cannot trust the native equality/inequality operators, the relation operators cannot be trusted neither.
double a = 1.0 / 6.0;
double b = a + a + a + a + a + a;
...
if (1.0 < b) ... ...
if (b < 1.0) ...
Likewise for all relation operators.
This calls for specific epsilon based relation functions as above for equality/inequality functions.
Make double Relations tolerant
Let's just start with a possible solution, based on the EQ/NE from above.
public static class RealExtensions
{
public struct Epsilon
{
public Epsilon(double value) { _value = value; }
private double _value;
internal bool IsEqual (double a, double b) { return (a == b) || (Math.Abs(a - b) < _value); }
internal bool IsNotEqual(double a, double b) { return (a != b) && !(Math.Abs(a - b) < _value); }
}
public static bool EQ(this double a, double b, Epsilon e) { return e.IsEqual (a, b); }
public static bool LE(this double a, double b, Epsilon e) { return e.IsEqual (a, b) || (a < b); }
public static bool GE(this double a, double b, Epsilon e) { return e.IsEqual (a, b) || (a > b); }
public static bool NE(this double a, double b, Epsilon e) { return e.IsNotEqual(a, b); }
public static bool LT(this double a, double b, Epsilon e) { return e.IsNotEqual(a, b) && (a < b); }
public static bool GT(this double a, double b, Epsilon e) { return e.IsNotEqual(a, b) && (a > b); }
}
Usage:
var epsilon = new RealExtension.Epsilon(1E-3);
...
double x = 1.0 / 6.0;
double y = x+x+x+x+x+x;
if (y.LT(1.0, epsilon)) ...
Notes:
- These relation functions work also for the special values.
*************** Now, that's all! Finally. *******************
Summary
Native equality and relation operations on floating point values cannot be trusted. A possible solution is to invent the six operations based on some epsilon value. Special care must be taken for the special values like Infinity
and NaN
.
What else?
The float
value type suffers the same problem. I.e. that type needs that same solution as for double
, adapted to float
.
The decimal
has a bit a different situation: it is not considered as a C# floating-point type but has similar issues with implicit rounding errors. I.e. the same solution (adapted to decimal
) will solve the problem here too.
Some useful links:
Revisions
Version | Date | Description |
V1.0 | 2012-05-13 | Initial version |
V1.1 | 2012-05-14 | Fix formatting |
V1.2 | 2012-05-14 | Added double.IsNaN(x) , double.IsPositiveInfinity(x) , and double.IsNegativeInfinity(x) |