Skip to content
C++ Better Explained
Go back
How to Compare Floating-Point Numbers in C++ (Why == Fails)

How to Compare Floating-Point Numbers in C++

Short answer: do not use ==. Compare the difference against a tolerance:

#include <cmath>
#include <limits>

bool almostEqual(double a, double b) {
    double diff = std::fabs(a - b);
    double scale = std::max(std::fabs(a), std::fabs(b));
    return diff <= std::numeric_limits<double>::epsilon() * scale * 4;
}

The Problem, Demonstrated

#include <iostream>
#include <iomanip>

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

    std::cout << (a == 0.3) << '\n';                    // 0 — false!
    std::cout << std::setprecision(20) << a << '\n';    // 0.30000000000000004441
}

This is not a C++ bug, and it is not your compiler. Every language using IEEE 754 doubles behaves the same way — Python, JavaScript, Java, C#.

Why It Happens

Binary fractions can represent halves, quarters and eighths exactly. They cannot represent one tenth, for the same reason decimal cannot represent one third — writing 0.333… never terminates.

So 0.1 in a double is not 0.1. It is the nearest representable binary value, which is very slightly off. Add two such approximations and the errors compound into something visible.

Integers up to 2⁵³ are stored exactly, which is why this surprise only appears with fractions.

Learning C++ properly? The C++ Better Explained Ebook covers types, precision and the STL in plain English — 87 pages, just $19.

The Naive Fix, and Why It Is Not Enough

// works for small numbers, fails for large ones
bool almostEqual(double a, double b) {
    return std::fabs(a - b) < 0.00001;
}

A fixed tolerance breaks down as magnitudes grow. At around 10¹⁶, consecutive doubles are more than 1 apart, so two genuinely different values can never differ by less than 0.00001 — and two identical-in-practice values may differ by far more.

The Scaled Tolerance Version

#include <algorithm>
#include <cmath>
#include <limits>

bool almostEqual(double a, double b, double factor = 4.0) {
    double diff = std::fabs(a - b);
    if (diff <= std::numeric_limits<double>::min()) return true;  // both ~0

    double scale = std::max(std::fabs(a), std::fabs(b));
    return diff <= std::numeric_limits<double>::epsilon() * scale * factor;
}

int main() {
    std::cout << almostEqual(0.1 + 0.2, 0.3) << '\n';       // 1
    std::cout << almostEqual(1e16, 1e16 + 1) << '\n';       // 1 — indistinguishable
    std::cout << almostEqual(1.0, 1.1) << '\n';             // 0
}

The tolerance now grows with the numbers, so it behaves sensibly at any magnitude. The factor controls how many representable steps apart you will still call “equal” — 4 is a reasonable default for ordinary arithmetic.

Note the early return: when both values are essentially zero, the scaled tolerance collapses to zero and would reject them, so that case needs handling separately.

Comparing Against Zero

Zero is the one case where a scaled tolerance cannot work, because there is no magnitude to scale by. Use a small absolute tolerance chosen for your problem:

bool isZero(double x, double tolerance = 1e-12) {
    return std::fabs(x) < tolerance;
}

What counts as “small” depends entirely on your domain — 1e-12 is meaningless if you are working in nanometres.

When == Is Actually Fine

Exact comparison is correct when no arithmetic has happened:

double x = 3.14;
if (x == 3.14) { ... }              // fine — same literal, no computation

double v = getSentinel();
if (v == std::numeric_limits<double>::infinity()) { ... }   // fine

It is arithmetic — especially repeated arithmetic in loops — that accumulates the error.

The NaN Exception

Not-a-number is never equal to anything, including itself:

double nan = std::numeric_limits<double>::quiet_NaN();

std::cout << (nan == nan) << '\n';      // 0
std::cout << std::isnan(nan) << '\n';   // 1 — the correct test

That self-inequality is actually the classic trick for detecting NaN without <cmath>.

Avoid Accumulating Error in Loops

// error grows with every iteration
for (double t = 0.0; t != 1.0; t += 0.1) { ... }   // may never terminate!

// count in integers instead
for (int i = 0; i < 10; ++i) {
    double t = i * 0.1;
}

The first loop can run forever because t never lands exactly on 1.0. Counting with an integer and deriving the double each time keeps the error from compounding.

When to Use a Different Type Entirely

For money, do not use double at all. Store cents as an integer:

long long cents = 1999;      // $19.99 — exact

Floating point is for measurement and science, where small relative error is acceptable. Currency needs exact decimal arithmetic.

Quick Reference

GoalCode
Compare two doublesalmostEqual(a, b) from above
Compare against zerostd::fabs(x) < 1e-12
Detect NaNstd::isnan(x)
Detect infinitystd::isinf(x)
Machine epsilonnumeric_limits<double>::epsilon()
Moneyuse integer cents, not double

Take Your C++ Further

If you want precision, types and the rest of the fundamentals explained properly, the C++ Better Explained Ebook covers them in plain English. Just $19.

👉 Get the C++ Better Explained Ebook — $19


📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Share this post on:

Written by

Sahil Bora

Software Engineer. Author and creator of C++ Better Explained.


Previous Post
Maximum Value of a double in C++ (DBL_MAX Explained)
Next Post
INT_MAX and INT_MIN in C++: The Maximum and Minimum int Value

Keep Learning