Question

Float vs Double in C++: Precision, Range, and When to Use Each

cppcfloating-pointprecisionieee-754

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:

  • float usually stores about 6 to 7 decimal digits of precision.
  • double usually stores about 15 to 16 decimal digits of precision.

On most modern systems, both use the IEEE 754 format:

TypeUsual sizeUsual precisionUsual approximate range
float4 bytes6–7 decimal digitsabout 1e-38 to 1e38
double8 bytes15–16 decimal digitsabout 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 float is like a ruler marked in millimeters. It is compact and good enough for many measurements, but it cannot describe extremely tiny differences.
  • A double is 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.

Take Quiz

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:

  1. f stores 100000000.0 as a float.
  2. At this size, the gap between nearby representable float values is larger than 1 on typical IEEE 754 systems.
  3. Adding 1.0f creates a result that cannot be represented as a distinct float, so it rounds back to 100000000.
  4. d has substantially more precision at the same magnitude.
  5. d + 1.0 can still be represented distinctly, producing 100000001.

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: double is often the simplest safe default for approximate real-number calculations.
  • C++ standard-library math functions: many functions, such as std::sin, use double overloads naturally when passed a double.

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 float values uses roughly half the memory of the same number of double values.
  • 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.

Take Quiz

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

AspectfloatdoublePractical guidance
Typical storage4 bytes8 bytesUse float for large memory-sensitive data when suitable.
Typical decimal precision6–7 digits15–16 digitsPrefer double for most calculations.
Typical rangeSmallerMuch largerUse double for very large or very small values.
Literal syntax1.5f1.5Decimal literals are double unless suffixed with f.
Rounding errorUsually largerUsually smallerNeither type stores every decimal exactly.
Common domainsGraphics buffers, compact samplesScientific code, application logicFollow API and domain requirements.

float and double versus integers

Type familyBest forImportant property
Integer types (int, long long)Counts, IDs, exact centsExact within their range
Floating-point types (float, double)Approximate measurements and fractionsFinite 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.

Take Quiz

Cheat Sheet

float  f = 3.14f; // Usually ~6–7 decimal digits
double d = 3.14;  // Usually ~15–16 decimal digits
  • Prefer double for ordinary C++ calculations unless you have a reason to use float.
  • Use float when an API requires it or when memory usage matters and its precision is enough.
  • A decimal literal is double by default: use 3.0f for a float literal.
  • 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more