Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 4 of 8

Integers are exact. Floating-point numbers are not, and pretending otherwise causes bugs that survive to production because they only show up in the last decimal place.

The demonstration

#include <iomanip>
#include <iostream>

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

    std::cout << (a + b) << "\n";
    std::cout << std::setprecision(20) << (a + b) << "\n";
    std::cout << ((a + b) == 0.3) << "\n";
    return 0;
}
0.3
0.30000000000000004441
0

The first line looks right because std::cout rounds to 6 significant digits by default. Ask for 20 and the truth appears — and the equality comparison says 0, meaning false.

This is not a C++ flaw. Every language using the standard hardware float format has it: Python, JavaScript, Java, C#. You saw the same thing in the Python track.

Why it happens

double stores numbers in binary fractions: sums of 1/2, 1/4, 1/8, and so on.

Some decimals convert perfectly — 0.5 is 1/2, 0.75 is 1/2 + 1/4. But 0.1 is a repeating fraction in binary, exactly as 1/3 is repeating in decimal. Writing 1/3 as 0.333… you must stop somewhere, and what you wrote is slightly wrong. double has 53 bits of precision, so it stops there, and 0.1 is stored as something very close to 0.1 but not equal to it.

Add two slightly-wrong numbers and the wrongness compounds.

Never compare floats with ==

Compare with a tolerance — is the difference small enough to not matter?

#include <cmath>
#include <iostream>

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

    std::cout << (a == b) << "\n";
    std::cout << (std::abs(a - b) < 1e-9) << "\n";
    return 0;
}
0
1

std::abs from <cmath> gives absolute value; 1e-9 is scientific notation for 0.000000001. Read the second line as "are these equal to within a billionth?"

Choosing the tolerance is a judgement call. 1e-9 is reasonable for numbers near 1. For very large values you'd want a relative tolerance — comparing the difference to the size of the numbers — because 1e-9 is meaninglessly strict when your values are in the billions.

What double cannot do

Don't use floating point for money. Ever. Prices in double accumulate rounding error, and financial reports that are one cent off are both embarrassing and, in some jurisdictions, illegal.

Store money as an integer count of the smallest unit — cents, riels — and divide only when displaying:

#include <iostream>

int main() {
    long long price_cents = 1999;      // $19.99
    long long quantity = 3;
    long long total_cents = price_cents * quantity;

    std::cout << total_cents << "\n";
    std::cout << (total_cents / 100) << "." << (total_cents % 100) << "\n";
    return 0;
}
5997
59.97

Exact, always. (A production version would handle the case where cents is under 10 and needs a leading zero — that's presentation, not arithmetic.)

The three special values

Floating point has values that aren't numbers at all:

#include <cmath>
#include <iostream>
#include <limits>

int main() {
    double inf = std::numeric_limits<double>::infinity();
    double nan = std::numeric_limits<double>::quiet_NaN();

    std::cout << inf << " " << (-inf) << " " << nan << "\n";
    std::cout << (1.0 / inf) << "\n";
    std::cout << (nan == nan) << "\n";
    std::cout << std::isnan(nan) << " " << std::isinf(inf) << "\n";
    return 0;
}
inf -inf nan
0
0
1 1
  • Infinity — produced by overflow, or by dividing a nonzero number by zero.
  • Negative infinity — the same, going the other way.
  • NaN ("not a number") — produced by genuinely meaningless operations like 0.0/0.0 or std::sqrt(-1.0).

NaN is not equal to itself. Look at that third line: nan == nan is false. That's not a bug — it's specified, and it's why you must use std::isnan() to test for it. It also means a single NaN entering a calculation silently poisons everything downstream, since every comparison involving it is false.

Note that dividing an integer by zero is different: that's undefined behaviour and usually crashes, rather than producing infinity.

Try it

Write a function-free program that adds 0.1 to a running total ten times and compares the result to 1.0, both with == and with a tolerance. Print both answers.

Checkpoint

  • double stores binary fractions, so most decimals (including 0.1) are stored slightly wrong.
  • Never compare floating-point values with ==; compare the absolute difference to a tolerance.
  • Never use floating point for money — use integer counts of the smallest unit.
  • std::setprecision reveals the digits std::cout hides by default.
  • Infinity and NaN are real values; NaN is not equal to itself, so test with std::isnan().

Questions about this lesson

Log in to ask a question.