C#
double comparison
floating-point precision
programming
software development

Comparing double values in C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Comparing double values directly is unreliable in C# because many decimal values cannot be represented exactly in binary floating-point form. The usual fix is not "never compare doubles," but "compare them with a tolerance that matches the scale of the problem."

Why == Often Fails

double follows the IEEE 754 binary floating-point format. That means apparently simple decimal arithmetic can produce tiny representation errors.

csharp
1double a = 0.1 + 0.2;
2double b = 0.3;
3
4Console.WriteLine(a == b);  // False
5Console.WriteLine(a);       // 0.30000000000000004

The numbers are mathematically equal, but their stored bit patterns differ slightly. Direct equality therefore gives the wrong answer for many real-world calculations.

Use an Absolute Tolerance for Small Values

For values close to zero, an absolute tolerance is often enough.

csharp
1static bool NearlyEqualAbsolute(double left, double right, double tolerance = 1e-9)
2{
3    return Math.Abs(left - right) <= tolerance;
4}
5
6Console.WriteLine(NearlyEqualAbsolute(0.1 + 0.2, 0.3));  // True

This says "treat the numbers as equal if the gap is smaller than a fixed threshold." It works well when all expected values live in roughly the same small numeric range.

Use Relative Tolerance for Larger Magnitudes

Absolute tolerance breaks down when numbers become very large. A difference of 0.000001 might be huge in one context and irrelevant in another. Relative tolerance scales the comparison to the magnitude of the values.

csharp
1static bool NearlyEqual(double left, double right, double tolerance = 1e-9)
2{
3    double diff = Math.Abs(left - right);
4    double scale = Math.Max(1.0, Math.Max(Math.Abs(left), Math.Abs(right)));
5    return diff <= tolerance * scale;
6}
7
8Console.WriteLine(NearlyEqual(1_000_000.000001, 1_000_000.000002));  // True
9Console.WriteLine(NearlyEqual(100.0, 100.1));                        // False

This pattern is a better default for general-purpose numeric code because it handles both small and large values more gracefully.

When Exact Equality Is Fine

Not every double comparison needs a tolerance. Exact equality is appropriate when:

  • you are comparing against NaN, Infinity, or -Infinity using the proper APIs
  • the values were assigned from the same computation or exact bit-preserving source
  • the values represent discrete states encoded as doubles, though that design is uncommon

Examples:

csharp
1double value = double.PositiveInfinity;
2
3Console.WriteLine(double.IsInfinity(value));  // True
4Console.WriteLine(double.IsNaN(value));       // False

For normal arithmetic results, though, tolerance-based comparison is the safer assumption.

Prefer decimal for Money

Sometimes the correct answer is to stop using double. Financial calculations usually need decimal arithmetic rather than binary floating point.

csharp
1decimal subtotal = 0.1m + 0.2m;
2decimal expected = 0.3m;
3
4Console.WriteLine(subtotal == expected);  // True

decimal is slower and has a smaller numeric range than double, but it represents base-10 fractions much more naturally. If the domain is currency, accounting, or invoicing, that tradeoff is usually correct.

A Reusable Helper

For engineering code, it is often worth centralizing the comparison rule:

csharp
1public static class FloatComparison
2{
3    public static bool NearlyEqual(double left, double right, double tolerance = 1e-9)
4    {
5        if (double.IsNaN(left) || double.IsNaN(right))
6            return false;
7
8        if (double.IsInfinity(left) || double.IsInfinity(right))
9            return left.Equals(right);
10
11        double diff = Math.Abs(left - right);
12        double scale = Math.Max(1.0, Math.Max(Math.Abs(left), Math.Abs(right)));
13        return diff <= tolerance * scale;
14    }
15}

That avoids inconsistent comparison logic scattered across the codebase.

Common Pitfalls

  • Using == after floating-point arithmetic and expecting mathematically equal values to share identical bit patterns.
  • Choosing a tolerance by habit instead of from the scale and accuracy requirements of the domain.
  • Using only an absolute tolerance when the values can vary across very different magnitudes.
  • Forgetting to handle special values such as NaN and infinity explicitly.
  • Continuing to use double in money-like calculations where decimal is the better numeric type.

Summary

  • Direct double equality is unreliable after most arithmetic.
  • Use absolute tolerance for small-scale values and relative tolerance for general cases.
  • Pick a tolerance based on the domain, not by guesswork.
  • Use decimal instead of double for money-like values.
  • Handle NaN and infinity explicitly in reusable comparison helpers.

Course illustration
Course illustration

All Rights Reserved.