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
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.
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) << ;
std::cout << (a, b) << ;
}
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
-
diff = std::abs(a - b)a - bis about-0.0001
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.
((result, , , ));
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, , , ))
{
std::();
}
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
{
std::(a - b) < ;
}
Comparisons
Here is a practical comparison of common approaches.
| Approach | Example | Good for | Weakness |
|---|---|---|---|
| Exact equality | a == b | exact checks, controlled values | unreliable for computed floating-point results |
| Absolute tolerance only | abs(a - b) < eps | values near zero, fixed small scales | poor for very large values |
| Relative tolerance only | diff <= max(abs(a), abs(b)) * eps | large or varying magnitudes | weak near zero |
| Absolute + relative tolerance | combine both checks | most general-purpose numeric comparisons | requires choosing tolerances |
float vs
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 computedfloatordoubleresults - 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
NaNexplicitly if it may appear
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?
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
nearlyEqualfunction fordoublevalues using absolute and relative tolerance. - Compare at least three pairs of values, including
0.1 + 0.2vs0.3. - Print the result of both exact equality and approximate equality.
- Include one example with large numbers and one example near zero.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.