Question
Float vs Double in C++: Precision, Range, and When to Use Each
Question
What is the difference between float and double in C++? Although single precision and double precision have different definitions, they often appear to produce the same result in simple programs. Are float and double truly interchangeable? When does the choice affect results, and what differences in precision, range, memory usage, and performance should I consider?
Short Answer
By the end of this page, you will understand why float and double can look equivalent in small examples but produce different results as values become very large, very small, or require many calculations. You will also learn practical rules for choosing the appropriate type in C++.
Concept
float and double are C++ floating-point types: they store numbers with a fractional part, such as 3.14 or -0.25.
The key difference is precision:
floatusually stores about 6 to 7 decimal digits of precision.doubleusually stores about 15 to 16 decimal digits of precision.
On most modern systems, both use the IEEE 754 format:
| Type | Usual size | Usual precision | Usual approximate range |
|---|---|---|---|
float | 4 bytes | 6–7 decimal digits | about 1e-38 to 1e38 |
double | 8 bytes | 15–16 decimal digits | about 1e-308 to 1e308 |
These are common implementations, not guarantees made by C++ itself. Use <limits> when your program needs the exact properties of its platform.
Floating-point values are stored in binary, not decimal. Many decimal values, including 0.1, cannot be represented exactly in binary. The stored value is therefore often the nearest available approximation.
double has more bits available for that approximation, so it usually has a smaller rounding error than float. In simple calculations, both approximations may round to the same displayed output, which can make the types appear interchangeable. They are not interchangeable when precision, range, storage size, external data formats, or an API's required type matters.
Mental Model
Think of float and double as measuring tools.
- A
floatis like a ruler marked in millimeters. It is compact and good enough for many measurements, but it cannot describe extremely tiny differences. - A
doubleis like a more detailed measuring instrument. It takes more space but can distinguish values that are much closer together.
If you measure a room's width, both may report 4.2 meters. If you measure the thickness of a small component or repeatedly add tiny adjustments, the extra detail of double becomes important.
Neither tool is infinitely precise. Some measurements must be rounded because floating-point types have a finite number of representable values.
Syntax and Examples
Declare float and double variables like this:
float temperature = 21.5f;
double distance = 1234567.890123;
The f suffix matters. A decimal literal such as 21.5 has type double by default. Writing 21.5f explicitly creates a float literal.
#include <iostream>
#include <iomanip>
int main() {
float single = 1.23456789f;
double precise = 1.23456789;
std::cout << std::setprecision(10);
std::cout << "float: " << single << '\n';
std::cout << "double: " << precise << '\n';
}
A typical output is similar to:
float: 1.234567881
double: 1.23456789
The float value has rounded because it cannot preserve all digits in the input. The double has enough precision for this particular number of decimal digits.
To inspect the guaranteed limits on the current platform, use std::numeric_limits:
#include <iostream>
#include <limits>
int main() {
std::cout << "float digits10: "
<< std::numeric_limits<float>::digits10 << '\n';
std::cout << "double digits10: "
<< std::numeric_limits<double>::digits10 << '\n';
}
Step by Step Execution
Consider adding a very small value to a large one:
#include <iostream>
#include <iomanip>
int main() {
float f = 100000000.0f;
double d = 100000000.0;
f = f + 1.0f;
d = d + 1.0;
std::cout << std::fixed << std::setprecision(0);
std::cout << "float: " << f << '\n';
std::cout << "double: " << d << '\n';
}
Step by step:
fstores100000000.0as afloat.- At this size, the gap between nearby representable
floatvalues is larger than1on typical IEEE 754 systems. - Adding
1.0fcreates a result that cannot be represented as a distinctfloat, so it rounds back to100000000. dhas substantially more precision at the same magnitude.d + 1.0can still be represented distinctly, producing100000001.
Typical output:
float: 100000000
double: 100000001
This is why the types may agree for small values but differ for large values or fine-grained changes.
Real World Use Cases
Use double when numerical accuracy is the default priority:
- Scientific and engineering calculations: measurements, simulations, geometry, statistics, and numerical algorithms.
- Backend services and data processing: averages, rates, coordinates, and aggregated measurements.
- General-purpose application code:
doubleis often the simplest safe default for approximate real-number calculations. - C++ standard-library math functions: many functions, such as
std::sin, usedoubleoverloads naturally when passed adouble.
Use float when reduced memory or compatibility is important and its precision is sufficient:
- Graphics and game engines: vertex positions, normals, colors, matrices, and GPU buffers often use 32-bit floats.
- Large arrays: an array of 100 million
floatvalues uses roughly half the memory of the same number ofdoublevalues. - File formats, network protocols, or hardware APIs: a specification may require 32-bit floating-point values.
- Machine-learning tensors: many workflows intentionally use lower-precision formats for memory and throughput, with careful numerical design.
For money, neither binary float nor double is usually ideal for exact currency calculations. Store the smallest currency unit in an integer, such as cents, or use an appropriate decimal representation when required.
Real Codebase Usage
In production C++ code, developers commonly make the type choice explicit at boundaries and keep calculations consistent.
Use double as the ordinary calculation type
double calculateAverage(double total, int count) {
if (count == 0) {
return 0.0;
}
return total / count;
}
This guard clause prevents division by zero. The 0.0 literal is a double, matching the return type.
Convert at a storage or API boundary
A graphics API may require float, while calculations happen in double:
struct Vertex {
float x;
float y;
};
Vertex makeVertex(double x, double y) {
return {
static_cast<float>(x),
static_cast<float>(y)
};
}
The explicit casts make the possible loss of precision visible during code review.
Compare approximately, not with ==
#include <cmath>
bool nearlyEqual(double a, double b, double epsilon = 1e-9) {
return std::abs(a - b) <= epsilon;
}
For values with widely different magnitudes, a relative tolerance is often more appropriate than one fixed epsilon.
Use the type required by containers and libraries
std::vector<float> samples;
std::vector<double> measurements;
Choose one intentionally. Repeatedly converting between types can add rounding and make code harder to reason about.
Common Mistakes
Assuming printed values are exact
float value = 0.1f;
value is usually close to 0.1, not exactly equal to the mathematical decimal value. Output formatting may hide the approximation.
Avoid it: treat floating-point values as approximations and choose a suitable output precision while debugging.
Comparing calculated floating-point values with ==
// Fragile: may be false because of rounding.
if (0.1 + 0.2 == 0.3) {
// ...
}
Avoid it: compare the difference against a tolerance when approximate equality is appropriate.
#include <cmath>
if (std::abs((0.1 + 0.2) - 0.3) < 1e-12) {
// Values are close enough for this calculation.
}
Forgetting the f suffix for a float literal
float rate = 0.75; // 0.75 is a double literal, then converted to float.
This may be harmless for many constants, but it can trigger compiler warnings in stricter builds and hides your intention.
Prefer:
float rate = 0.75f;
Expecting float to preserve tiny changes at every magnitude
float balance = 100000000.0f;
balance += 1.0f; // May not change balance.
Avoid it: use double, rescale units, or redesign the calculation when small increments must remain meaningful.
Using floating point for exact currency
double total = 0.1 + 0.2;
This can produce a value slightly different from 0.3 internally.
Avoid it: represent dollars as integer cents when the currency has fixed decimal places.
long long totalCents = 10 + 20;
Assuming sizes are guaranteed
float is commonly 4 bytes and double commonly 8 bytes, but portable code should not depend on that without checking.
Avoid it: use std::numeric_limits<T> for numeric properties and sizeof(T) if size is relevant on the current platform.
Comparisons
| Aspect | float | double | Practical guidance |
|---|---|---|---|
| Typical storage | 4 bytes | 8 bytes | Use float for large memory-sensitive data when suitable. |
| Typical decimal precision | 6–7 digits | 15–16 digits | Prefer double for most calculations. |
| Typical range | Smaller | Much larger | Use double for very large or very small values. |
| Literal syntax | 1.5f | 1.5 | Decimal literals are double unless suffixed with f. |
| Rounding error | Usually larger | Usually smaller | Neither type stores every decimal exactly. |
| Common domains | Graphics buffers, compact samples | Scientific code, application logic | Follow API and domain requirements. |
float and double versus integers
| Type family | Best for | Important property |
|---|---|---|
Integer types (int, long long) | Counts, IDs, exact cents | Exact within their range |
Floating-point types (float, double) | Approximate measurements and fractions | Finite precision and rounding |
If a value must be exact, such as a database identifier or a count of items, use an integer rather than either floating-point type.
Cheat Sheet
float f = 3.14f; // Usually ~6–7 decimal digits
double d = 3.14; // Usually ~15–16 decimal digits
- Prefer
doublefor ordinary C++ calculations unless you have a reason to usefloat. - Use
floatwhen an API requires it or when memory usage matters and its precision is enough. - A decimal literal is
doubleby default: use3.0ffor afloatliteral. - Floating-point values are approximations; many decimal fractions cannot be represented exactly.
- Do not normally compare computed floating-point values with
==. - Use an application-appropriate tolerance for approximate comparisons.
- Do not use binary floating point for exact money amounts; use integer minor units when possible.
- Check platform properties when needed:
#include <limits>
std::numeric_limits<float>::digits10;
std::numeric_limits<double>::digits10;
std::numeric_limits<double>::max();
- Make narrowing conversions explicit:
float compact = static_cast<float>(someDouble);
FAQ
Is double always better than float in C++?
Not always. double usually gives safer precision and range, so it is a good default. Use float when its lower memory usage, an external API, or a data format makes it the appropriate choice.
Why do float and double often produce the same output?
For simple values and calculations, both types may be accurate enough that their rounded results display identically. Differences become visible when you need more than about 6–7 significant decimal digits, use extreme magnitudes, or accumulate many operations.
Is float always 4 bytes and double always 8 bytes?
They are commonly those sizes, but C++ does not guarantee them. Check with sizeof on a target platform if storage size is important.
How many decimal places can a float store?
A typical float stores about 6–7 significant decimal digits, not a fixed number of digits after the decimal point. For example, it can represent 1234567 approximately, but it cannot also reliably preserve many fractional digits at that magnitude.
Why does 0.1 + 0.2 sometimes not equal 0.3?
The values 0.1, 0.2, and 0.3 usually have no exact finite binary representation. Each is rounded to a nearby binary value, and the rounded calculation can differ slightly from the rounded representation of 0.3.
Should I use an epsilon for every floating-point comparison?
No. Use approximate comparison when values come from calculations with expected rounding error. For values that represent discrete states, use a different representation or compare against a clearly defined threshold.
Can I assign a double to a float?
Yes, but it may lose precision or overflow to infinity if the value is outside the float range. Prefer an explicit static_cast<float> when narrowing is intentional.
Are floating-point calculations deterministic?
They are often repeatable in the same build and environment, but results can differ across hardware, compiler settings, operation ordering, or optimization choices. Code that depends on exact floating-point bit patterns requires extra care.
Mini Project
Description
Build a small measurement summary program that stores sensor readings as float values but calculates the total and average with double. This models a common real-world pattern: compact input storage combined with more precise aggregation.
Goal
Read several temperature readings, calculate their average with double, and show why the calculation type is chosen intentionally.
Requirements
Use a std::vector<float> containing at least five readings.
Calculate the total using a double accumulator.
Calculate and print the average with at least six digits after the decimal point.
Handle an empty list without dividing by zero.
Explain in a code comment why the accumulator is a double.
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.