Question

How to Compare Float and Double Values Safely in C++

cppalgorithmoptimizationfloating-point

Question

In C++, what is an efficient and correct way to compare two double values or two float values while accounting for floating-point precision loss?

A direct comparison like this is often unreliable:

bool CompareDoubles1(double a, double b)
{
    return a == b;
}

A tolerance-based approach is more appropriate:

bool CompareDoubles2(double a, double b)
{
    double diff = a - b;
    return (diff < EPSILON) && (-diff < EPSILON);
}

However, this may seem inefficient or incomplete. Is there a better or smarter way to compare floating-point numbers?

Short Answer

By the end of this page, you will understand why float and double values cannot always be compared safely with ==, how tolerance-based comparison works, when to use absolute vs relative tolerance, and how to write practical floating-point comparison functions in C++.

Concept

Floating-point numbers such as float and double are stored in binary, not decimal. Because of that, many decimal values cannot be represented exactly.

For example, a value like 0.1 looks simple in decimal, but its binary representation is only an approximation. That means calculations can produce tiny rounding differences.

double a = 0.1 + 0.2;
double b = 0.3;

// Often false
bool same = (a == b);

Even though the math looks equal, the stored values may differ by a very small amount.

This matters because real programs often use floating-point numbers for:

  • scientific calculations
  • graphics and game development
  • financial approximations
  • measurements and sensor data
  • percentages and statistics

A good comparison usually checks whether two numbers are close enough rather than exactly equal.

There are two common ideas:

  • Absolute tolerance: useful when numbers are near zero
  • Relative tolerance: useful when numbers may be very large or very small

A robust comparison often combines both.

Important note: exact comparison with == is not always wrong. It is acceptable when:

  • you intentionally want exact bit-pattern equality
  • values come from the same calculation path
  • comparing with 0.0 in some controlled cases
  • checking special values like infinity after known operations

But for most numeric results, tolerance-based comparison is the safer approach.

Mental Model

Think of floating-point numbers like measurements made with a ruler.

If one object measures 10.0000001 cm and another measures 10.0000002 cm, they are effectively the same for many purposes. A strict == comparison acts like saying those objects are different because the ruler showed slightly different marks.

A tolerance-based comparison is like saying:

  • "If the difference is tiny enough, treat them as equal."

Another way to picture it:

  • == asks: Are these numbers identical?
  • epsilon comparison asks: Are these numbers close enough to be considered the same?

The tricky part is deciding what “close enough” means. A difference of 0.0001 may be tiny for 1000000.0, but huge for 0.00001. That is why relative tolerance is often needed.

Take Quiz

Syntax and Examples

The simplest safe pattern uses std::abs and a tolerance value.

#include <cmath>

bool nearlyEqual(double a, double b, double epsilon = 1e-9)
{
    return std::abs(a - b) < epsilon;
}

This works well when the numbers are expected to be near the same scale and especially near zero.

Example

#include <iostream>
#include <cmath>

bool nearlyEqual(double a, double b, double epsilon = 1e-9)
{
    return std::abs(a - b) < epsilon;
}

int main()
{
    double a = 0.1 + 0.2;
    double b = 0.3;

    std::cout << std::boolalpha;
    std::cout << (a == b) << "\n";
    std::cout << nearlyEqual(a, b) << "\n";
}

Typical output:

false
true

Better: combine absolute and relative tolerance

A more practical comparison handles both small and large values.

#include <cmath>
#include <algorithm>

bool nearlyEqual(double a, double b, double absEpsilon = 1e-12, double relEpsilon = 1e-9)
{
    double diff = std::abs(a - b);

    if (diff <= absEpsilon)
        return true;

    return diff <= std::max(std::abs(a), std::abs(b)) * relEpsilon;
}

Why this version is better

  • absEpsilon helps when values are close to zero
  • relEpsilon scales the allowed difference for large values
  • it works more reliably across different magnitudes

Float version

bool nearlyEqual(float a, float b, float absEpsilon = 1e-6f, float relEpsilon = 1e-5f)
{
    float diff = std::fabs(a - b);

    if (diff <= absEpsilon)
        return true;

    return diff <= std::max(std::fabs(a), std::fabs(b)) * relEpsilon;
}

The exact tolerance values depend on your application. There is no universal epsilon that fits every problem.

Step by Step Execution

Consider this example:

#include <cmath>
#include <algorithm>

bool nearlyEqual(double a, double b, double absEpsilon = 1e-12, double relEpsilon = 1e-9)
{
    double diff = std::abs(a - b);

    if (diff <= absEpsilon)
        return true;

    return diff <= std::max(std::abs(a), std::abs(b)) * relEpsilon;
}

Now call it like this:

double a = 1000000.0;
double b = 1000000.0001;

bool result = nearlyEqual(a, b);

Step by step

  1. diff = std::abs(a - b)

    • a - b is about -0.0001
    • absolute difference becomes 0.0001
  2. Check absolute tolerance:

    • 0.0001 <= 1e-12 is false
    • so the values are not close enough by absolute tolerance alone
  3. Compute relative scale:

    • std::max(std::abs(a), std::abs(b)) is about 1000000.0001
  4. Multiply by relative tolerance:

    • 1000000.0001 * 1e-9 is about 0.001
  5. Final comparison:

    • 0.0001 <= 0.001 is true
  6. Function returns true

This makes sense because for numbers around one million, a difference of 0.0001 is extremely small.

Now compare numbers near zero:

double x = 0.0000000000001;
double y = 0.0;

Here, relative comparison alone can behave poorly because both numbers are tiny. That is why the absolute tolerance check comes first.

Real World Use Cases

Floating-point comparison appears in many practical situations.

Graphics and games

Positions, rotations, and physics values are often stored as float.

if (nearlyEqual(playerX, targetX, 1e-4f, 1e-4f))
{
    // Player has effectively reached the target position
}

Scientific and engineering calculations

Measurements and simulations produce approximate results.

if (nearlyEqual(simulatedValue, expectedValue, 1e-10, 1e-8))
{
    // Result is within acceptable error
}

Data processing

When reading decimal values from files, APIs, or sensors, minor representation differences are common.

if (nearlyEqual(temperatureReading, 25.0, 1e-6, 1e-6))
{
    // Treat as 25 degrees
}

Testing numeric code

Unit tests for mathematical functions should usually avoid strict equality.

ASSERT_TRUE(nearlyEqual(result, 3.1415926535, 1e-12, 1e-9));

Finance with caution

Floating-point values are usually not ideal for exact money calculations, but if they are used for approximations, tolerance-based comparison is necessary.

if (nearlyEqual(taxEstimate, expectedTax, 1e-6, 1e-6))
{
    // Acceptable estimate
}
Take Quiz

Real Codebase Usage

In real codebases, developers usually wrap floating-point comparison in a helper function instead of repeating the logic everywhere.

Common pattern: utility function

namespace math_utils
{
    bool nearlyEqual(double a, double b, double absEpsilon = 1e-12, double relEpsilon = 1e-9)
    {
        double diff = std::abs(a - b);

        if (diff <= absEpsilon)
            return true;

        return diff <= std::max(std::abs(a), std::abs(b)) * relEpsilon;
    }
}

This improves:

  • readability
  • consistency
  • easier tuning of tolerances

Guard clauses

Developers often use guard clauses before expensive numeric work.

if (nearlyEqual(length, 0.0))
    return;

Validation

if (!nearlyEqual(totalPercentage, 100.0, 1e-9, 1e-9))
{
    throw std::runtime_error("Percentages must add up to 100");
}

Testing helpers

Many codebases create custom assertions for numeric tests.

void assertNearlyEqual(double actual, double expected)
{
    if (!nearlyEqual(actual, expected))
    {
        throw std::runtime_error("Values are not close enough");
    }
}

Domain-specific tolerances

A physics engine, graphics tool, and statistics library may all use different tolerances. Real projects usually choose tolerances based on domain requirements, not on a single hardcoded global epsilon.

Performance note

The comparison itself is already very cheap. In most applications, the cost of a few abs and max operations is negligible compared with the benefit of correctness. The smarter comparison is usually the one that gives reliable results, not the one with the fewest operations.

Common Mistakes

Here are common beginner mistakes when comparing floating-point numbers.

1. Using == for computed results

Broken example:

double a = 0.1 + 0.2;
double b = 0.3;

if (a == b)
{
    // May never run
}

Better:

if (nearlyEqual(a, b))
{
    // Safer
}

2. Using only absolute tolerance for all cases

Broken example:

bool nearlyEqual(double a, double b)
{
    return std::abs(a - b) < 1e-9;
}

Problem:

  • works for some values
  • fails when numbers are very large

Use combined absolute and relative tolerance instead.

3. Choosing a tolerance that is too large

bool nearlyEqual(double a, double b)
{
    return std::abs(a - b) < 0.1;
}

This may mark clearly different values as equal.

Avoid this by choosing tolerances based on the problem domain.

4. Choosing a tolerance that is too small

bool nearlyEqual(double a, double b)
{
    return std::abs(a - b) < 1e-30;
}

This can be nearly as strict as == for normal applications.

5. Forgetting values near zero

Relative comparison alone can break near zero.

Broken idea:

bool nearlyEqual(double a, double b)
{
    double diff = std::abs(a - b);
    return diff <= std::max(std::abs(a), std::abs(b)) * 1e-9;
}

If both values are near 0, the right side may become too small. Add an absolute tolerance check first.

6. Assuming std::numeric_limits<double>::epsilon() solves everything

Machine epsilon is the spacing near 1.0, not a universal comparison tolerance for all values.

#include <limits>

double eps = std::numeric_limits<double>::epsilon();

This value is useful, but not automatically the right threshold for your application.

7. Ignoring NaN

NaN is never equal to anything, not even itself.

double x = std::nan("1");

bool a = (x == x); // false

If NaN is possible in your program, handle it explicitly.

Comparisons

Here is a practical comparison of common approaches.

ApproachExampleGood forWeakness
Exact equalitya == bexact checks, controlled valuesunreliable for computed floating-point results
Absolute tolerance onlyabs(a - b) < epsvalues near zero, fixed small scalespoor for very large values
Relative tolerance onlydiff <= max(abs(a), abs(b)) * epslarge or varying magnitudesweak near zero
Absolute + relative tolerancecombine both checksmost general-purpose numeric comparisonsrequires choosing tolerances

float vs double

TypePrecisionTypical use
floatlower precisiongraphics, large arrays, performance-sensitive cases
doublehigher precisiongeneral calculations, scientific code, most numeric applications

epsilon vs application tolerance

TermMeaning
machine epsilonthe gap between 1.0 and the next representable value
application tolerancethe acceptable error for your specific problem

In real programs, application tolerance matters more than machine epsilon alone.

Take Quiz

Cheat Sheet

#include <cmath>
#include <algorithm>

bool nearlyEqual(double a, double b, double absEpsilon = 1e-12, double relEpsilon = 1e-9)
{
    double diff = std::abs(a - b);

    if (diff <= absEpsilon)
        return true;

    return diff <= std::max(std::abs(a), std::abs(b)) * relEpsilon;
}

Quick rules

  • Do not use == for most computed float or double results
  • Use std::abs(a - b) to measure the difference
  • Use absolute tolerance for values near zero
  • Use relative tolerance for large magnitudes
  • Combine both for general-purpose comparison
  • Pick tolerances based on your problem, not blindly
  • std::numeric_limits<T>::epsilon() is not a universal answer
  • Handle NaN explicitly if it may appear

Useful defaults

For double:

absEpsilon = 1e-12
relEpsilon = 1e-9

For float:

absEpsilon = 1e-6f
relEpsilon = 1e-5f

Near-zero pattern

if (std::abs(x) < 1e-12)
{
    // Treat as zero
}

Bad pattern

return a == b;

Better pattern

return nearlyEqual(a, b);

FAQ

Should I ever use == with doubles in C++?

Yes, in controlled situations such as exact constants, special-case logic, or when exact identity is intended. For computed results, use tolerance-based comparison.

Is std::numeric_limits<double>::epsilon() the right tolerance?

Not usually by itself. It describes floating-point spacing near 1.0, not the acceptable error for all values or all applications.

What tolerance should I use for comparing floats?

It depends on the domain. A common starting point is around 1e-6f absolute and 1e-5f relative, then adjust based on your required accuracy.

Why do I need both absolute and relative tolerance?

Absolute tolerance works well near zero. Relative tolerance works well for large values. Using both makes the comparison more reliable across different scales.

Is the tolerance-based method slower than ==?

Yes, slightly, but the difference is tiny in most programs. Correctness is usually much more important than saving a few arithmetic operations.

How do I compare values to zero?

Usually with an absolute tolerance:

std::abs(x) < 1e-12

What happens if one value is NaN?

Comparisons involving NaN are special. NaN == NaN is false, and tolerance-based checks should handle NaN explicitly if it may occur.

Should I use float or double?

Use double by default for general numeric work unless you have a specific reason to use float, such as memory usage or graphics APIs.

Take Quiz

Mini Project

Description

Build a small C++ program that compares pairs of floating-point numbers and reports whether they are approximately equal. This project demonstrates why strict equality is unreliable and how a reusable comparison helper improves correctness.

Goal

Create a console program that compares several double values using both == and a safe tolerance-based function.

Requirements

  • Write a nearlyEqual function for double values using absolute and relative tolerance.
  • Compare at least three pairs of values, including 0.1 + 0.2 vs 0.3.
  • Print the result of both exact equality and approximate equality.
  • Include one example with large numbers and one example near zero.
Take Quiz

Keep learning

Related questions

View more