C Part I · Module 2 · Values, Variables, Types and Representation
Floating-Point Types and What They Cannot Represent
Every type in this module so far has held whole numbers exactly. This chapter introduces the first that cannot — one that trades exactness for reach, covering a vast range of magnitudes at the cost of recording most values approximately rather than precisely. That trade produces the single most reported and least understood behaviour in programming: adding 0.1 to 0.2 does not give 0.3. Nothing is malfunctioning when that happens, and the explanation is a fact about numbers rather than about C. Once you have it you will also have the honest version of the advice about comparing these values, which is more useful and considerably narrower than the rule people usually repeat.
Foundation12 min readCPart IFundamentalsRepresentation
Part I · Module 2 · Chapter 2.9 · Floating-Point Types and What They Cannot Represent
1. A Different Kind of Trade
The integer types in this module all share a property: within their range, they record every value exactly. An int holding 42 holds exactly 42.
Some work needs values that are not whole numbers, and needs them across wildly different magnitudes — a fraction of a millimetre and a distance in kilometres, in the same program. C's floating-point types are for that, and they make a different trade:
An enormous range of magnitudes, at the cost of recording most values approximately rather than exactly.
C provides three, in increasing precision: float, double, and long double. Use double unless you have a specific reason not to. It is the type a floating-point literal like 3.14 already has, it is what the library expects, and float's reduced precision buys you storage you rarely need. This chapter uses double throughout.
2. The Behaviour That Looks Like a Bug
#include <stdio.h>
int main(void)
{
double a = 0.1;
double b = 0.2;
double sum = a + b;
printf("a + b at 17 digits: %.17f\n", sum);
printf("a + b at 1 digit : %.1f\n", sum);
printf("is it exactly 0.3? %d\n", sum == 0.3);
return 0;
}Output on the machine this chapter was written on:
a + b at 17 digits: 0.30000000000000004
a + b at 1 digit : 0.3
is it exactly 0.3? 0Three lines, each telling you something different.
The first shows the value to seventeen decimal places, and it is not 0.3. The second shows the same value to one place, and looks perfectly normal — the discrepancy was always there and rounding on display hid it. The third asks C directly whether the sum equals 0.3, and the answer is no.
The exact digits after 0.3000... depend on your implementation's floating-point format. The fact that there are some is not implementation-specific at all, and §3 is why.
3. Why — and It Is Not About Computers
Write one-third as a decimal:
1/3 = 0.3333333333...It never terminates. You cannot write one-third exactly in decimal, no matter how many digits you allow, because 3 does not divide evenly into the powers of ten that decimal is built from. Nobody finds this alarming. It is a fact about the relationship between the number and the notation.
Floating-point values are recorded in binary, and binary is built from powers of two. So the question for any value is: can it be written as a terminating sum of halves, quarters, eighths and so on?
0.5 = 1/2 exact
0.25 = 1/4 exact
0.75 = 1/2 + 1/4 exact
0.1 = 1/10 NOT exact — 10 is not a power of twoOne-tenth in binary is the same situation as one-third in decimal: an endlessly repeating pattern. And storage is finite, so what gets recorded is the closest value the format can represent — very close to 0.1, and not 0.1.
Do that twice and add, and the small discrepancies combine into one large enough to see at the seventeenth decimal place.
This is also Chapter 2.4's value-versus-representation distinction one more time. The value one-tenth is perfectly definite. Its representation in a finite number of binary digits is not exact, and the gap between those two is the entire subject of this chapter.
4. The Advice Everyone Gives, Corrected
The rule you will hear is: never compare floating-point values with ==.
It is memorable, it prevents the bug in §2, and it is wrong as stated. Here is why:
#include <stdio.h>
int main(void)
{
double half = 0.5;
double quarter = 0.25;
printf("is 0.5 + 0.25 exactly 0.75? %d\n", half + quarter == 0.75);
return 0;
}Output:
is 0.5 + 0.25 exactly 0.75? 11 means true. The comparison is exact, reliable and will be true on any implementation using binary floating point — because, per §3, halves and quarters are exactly representable and their sum is too.
So the operator was never the problem. The honest rule is about which values you are comparing:
Comparing floating-point values for equality is reliable when the values are exactly representable and were computed without loss. It is unreliable otherwise — which, for anything derived from decimal fractions or a chain of arithmetic, is most of the time.
In practice, that means: do not compare a computed result against a decimal fraction and expect exactness. Comparing against 0, or against a value you stored and did not compute with, is a different situation.
5. Where Floating Point Belongs
Two practical consequences follow from §1's trade, and both matter more than the == question.
Do not use floating point for money. Currency is exact and decimal, and floating point is neither. Prices, totals and balances are held as integers in the smallest unit — cents rather than dollars — precisely so that the discrepancies in §2 cannot occur.
Do not use floating point for counting. A count is a whole number and an integer type records it exactly. Reaching for double because a value "might need a fraction later" gives up exactness for a need you do not have.
Floating point is for measurement and for computation over wide ranges of magnitude — physical quantities, signal values, anything where the input was approximate to begin with and a tiny relative error is meaningless. Used there, it is excellent.
6. What You Should Be Able to Say Now
- Floating-point types trade exactness for range: enormous magnitudes, most values recorded approximately.
- C has
float,doubleandlong double. Usedoubleunless you have a reason not to. 0.1 + 0.2is not exactly 0.3, and displaying it at low precision hides the difference.- The cause is the notation, not the computer. One-tenth has no terminating binary form, exactly as one-third has no terminating decimal form.
- Values that are exactly representable — halves, quarters, and sums of them — compare exactly and reliably.
- So the real rule is about which values, not about the
==operator. - The exact digits of an inexact result depend on the implementation's format; the existence of a difference does not.
- Do not use floating point for money or for counting. Use it for measurement and wide-ranging computation.
7. Exercise
Exercise 1 — predict exactness. For each, say whether the value can be recorded exactly in binary floating point, and why: 0.5, 0.125, 0.2, 3.0, 0.75.
Exercise 2 — predict the comparison. Will each print 1 or 0? Justify each from §3 before running them.
printf("%d\n", 0.25 + 0.25 == 0.5);
printf("%d\n", 1.0 + 2.0 == 3.0);
printf("%d\n", 0.1 + 0.1 + 0.1 == 0.3);Then try 0.1 + 0.1 == 0.2 as well. It prints 1, which may surprise you after the third line printed 0. You are not expected to explain that one — note it as evidence that "is this comparison exact?" is a real question about particular values rather than something you can answer by spotting a decimal fraction.
Exercise 3 — explain the hiding. In §2 the same value printed as 0.30000000000000004 and as 0.3. Explain in two sentences why both outputs are correct and which one is misleading.
Exercise 4 — correct the rule. A colleague's coding standard says "Floating-point values must never be compared with ==." Write a two-sentence replacement that is accurate.
Exercise 5 — choose the type. For each, say whether you would use an integer type or double, and why: a bank balance; the temperature from a sensor; the number of files processed; the distance between two points.
Exercise 6 — extend the analogy. Decimal cannot write 1/3 exactly. Binary cannot write 1/10 exactly. Name a fraction that both can write exactly, and say what property it has.
8. Summary
- Floating point trades exactness for range. That trade is the source of everything else in this chapter.
- Use
double.floatsaves storage you rarely need;doubleis what literals and the library already are. 0.1 + 0.2is not 0.3, and low-precision display hides it rather than fixing it.- The cause is the base, not the machine. One-tenth in binary is one-third in decimal.
- Some values are exact — halves, quarters and their sums — and compare reliably.
- The
==rule is about values, not the operator. Comparing computed decimal fractions is unreliable; comparing exactly representable values is not. - Not for money, not for counting. For measurement and wide ranges of magnitude.
Every value in this module has been a number of some kind. The next chapter is about a value that is not a quantity at all — true or false — and about the fact that C spent most of its life without a dedicated type for it, which explains a truth model that would otherwise look eccentric.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
