AnyLearn
All lessons
Computer Scienceintermediate

How a Float Is Stored, and What That Rules Out

Floating point is not a slightly inaccurate version of real numbers. It is a finite set of values, spaced logarithmically, with exact rules. This lesson builds the IEEE 754 layout, shows why 0.1 cannot be represented, and derives the one error bound that every later result depends on.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 8

A finite set pretending to be the reals

A 64-bit double can hold at most 2 to the 64 distinct values. The real numbers between 0 and 1 alone are uncountably infinite. So the mapping is not an approximation that could be improved with more care; it is a finite set of representable values, and everything else rounds to one of them.

The useful mental model is a ruler with uneven markings. Near zero the markings are dense; far from zero they are far apart. Any computation lands between markings and gets snapped to the nearest one.

This is a deliberate design rather than a compromise. Fixed-point arithmetic spaces its values evenly, which wastes precision on large numbers and runs out of it on small ones. Floating point spends its bits on relative precision instead: roughly the same number of significant digits everywhere, across a range from about 10 to the minus 308 up to 10 to the 308.

The consequences everyone trips over are all downstream of that one choice.

Full lesson text

All 8 steps on one page, for reading, reference, and search.

Show

1. A finite set pretending to be the reals

A 64-bit double can hold at most 2 to the 64 distinct values. The real numbers between 0 and 1 alone are uncountably infinite. So the mapping is not an approximation that could be improved with more care; it is a finite set of representable values, and everything else rounds to one of them.

The useful mental model is a ruler with uneven markings. Near zero the markings are dense; far from zero they are far apart. Any computation lands between markings and gets snapped to the nearest one.

This is a deliberate design rather than a compromise. Fixed-point arithmetic spaces its values evenly, which wastes precision on large numbers and runs out of it on small ones. Floating point spends its bits on relative precision instead: roughly the same number of significant digits everywhere, across a range from about 10 to the minus 308 up to 10 to the 308.

The consequences everyone trips over are all downstream of that one choice.

2. Three fields, and what each buys

The IEEE 754 double splits 64 bits into three parts, and the split is the design.

Sign, 1 bit. Exponent, 11 bits, stored with a bias so it can represent both positive and negative powers. Significand, 52 stored bits, representing the digits.

The value is the significand times two raised to the exponent, which is where the name comes from: the binary point floats according to the exponent rather than sitting at a fixed position.

One detail carries real weight. For normal numbers, the significand is understood to begin with a 1 that is not stored, because in binary a normalised number always starts with 1. That gives 53 bits of precision from 52 bits of storage, a free bit that shows up in every precision calculation.

So the exponent controls range and the significand controls precision, and the two are independent. Single precision splits the same way with 8 and 23 bits, giving 24 bits of precision and a much smaller range. Choosing between them is choosing how many significant digits you need, not how large your numbers are.

3. Why 0.1 does not exist

The most reported bug in computing is not a bug. It is base conversion.

A binary fraction can only represent numbers whose denominators are powers of two. One half, one quarter, three eighths: exact. One tenth requires a denominator of ten, which has a factor of five, and no power of two contains a factor of five. So 0.1 has an infinitely repeating binary expansion, exactly as one third has an infinitely repeating decimal one.

Storing it truncates that expansion, and the stored value is not 0.1.

from decimal import Decimal
Decimal(0.1)
# 0.1000000000000000055511151231257827021181583404541015625

0.1 + 0.2
# 0.30000000000000004
0.1 + 0.2 == 0.3
# False

The printed 0.1 is a courtesy: languages display the shortest decimal string that round-trips back to the same double. The value underneath has always been the long one.

So 0.1 + 0.2 is not slightly wrong. It is the exact sum of two values that were never 0.1 and 0.2, correctly rounded, and it differs from the double nearest to 0.3.

4. The spacing changes with magnitude

The gap between consecutive doubles doubles every time the exponent increases, so it scales with the magnitude of the number.

Read the last two boxes together. Above roughly 2 to the 53, the spacing between representable doubles exceeds 1, so consecutive integers are no longer all representable. Adding 1 to such a number rounds straight back to the number itself.

That is not a rounding error in the usual sense; the result is the correctly rounded answer, and the correctly rounded answer is that nothing happened. A loop incrementing a double past 2 to the 53 never terminates, and no warning is produced anywhere.

The same fact explains why identifiers must not be stored as doubles. A 64-bit integer identifier above 2 to the 53 loses its low bits when passed through a double, which is why JSON parsers that decode all numbers as doubles silently corrupt large IDs.

flowchart LR
A["Near 1.0"] --> B["Gap is about 2.2e-16"]
C["Near 1000"] --> D["Gap is about 2.3e-13"]
E["Near 1e16"] --> F["Gap is about 2.0"]
F --> G["Adding 1.0 changes nothing"]

5. Machine epsilon and the one bound that matters

The quantity that summarises all of this is machine epsilon: the gap between 1.0 and the next representable double.

import sys
sys.float_info.epsilon        # 2.220446049250313e-16
sys.float_info.epsilon == 2 ** -52   # True

Its value is 2 to the minus 52, which follows directly from having 52 stored significand bits.

From it comes the fundamental guarantee of IEEE 754, and every result later in this cursus rests on it. For any single arithmetic operation on representable inputs, the computed result equals the exact result times (1 + d), where the magnitude of d is at most half of epsilon.

In words: each individual operation is correctly rounded, giving a relative error bounded by about 1.1 times 10 to the minus 16.

That is a strong promise, and it is worth noticing how narrow it is. It covers one operation. It says nothing about a sequence of them, and it is stated in relative terms, which is exactly the loophole the next lesson exploits.

6. The special values, and why they exist

Some bit patterns are reserved, and each solves a specific problem that would otherwise require aborting the computation.

Infinities. Overflow produces plus or minus infinity rather than an error. This lets a computation continue and produce a recognisable result instead of trapping, and infinities propagate sensibly through most arithmetic.

NaN, not a number. Produced by genuinely undefined operations: zero divided by zero, infinity minus infinity, the square root of a negative. Its defining property is that it compares unequal to everything, including itself. That looks perverse and is deliberate: it means x != x is a reliable NaN test, and it prevents undefined values from silently comparing as equal.

Signed zero. Both plus and minus zero exist and compare equal, but they behave differently in division, preserving the sign of an underflowed quantity.

Subnormals. Numbers too small for the normal exponent range degrade gracefully toward zero rather than jumping to it. This preserves the property that a - b == 0 implies a == b, which would otherwise fail near the underflow boundary.

Each exists so a computation can continue and report what happened, rather than stopping.

7. When not to use floating point

Floating point is the right tool for measured quantities and the wrong one for several common cases.

Money. Currency has exact decimal semantics and legal rounding rules, and 0.1 not being representable means a cent can be lost per operation. Use integer minor units, cents rather than dollars, or a decimal type. This is the single most common misuse.

Identifiers. Anything above 2 to the 53 loses low bits. Identifiers are not quantities and should not be arithmetic types at all.

Counting. Integers are exact and have no rounding, so a counter should be an integer.

Exact comparison for equality. Covered in the third lesson, but the short version is that two computations of mathematically equal quantities routinely produce different doubles.

What floating point is genuinely good at is anything where inputs are already approximate: physical measurements, statistics, geometry, simulation, machine learning. There, an input measured to four significant figures is being processed with sixteen, and rounding is not the limiting factor.

The distinction to carry forward is whether your quantities are exact by definition or approximate by nature. The first kind belongs in integers or decimals; the second is what floating point was built for.

8. What is now fixed

FactConsequence
Finite set of valuesEverything else rounds to a neighbour
Logarithmic spacingRelative precision constant, absolute precision is not
53 bits of precisionAbout 15 to 17 significant decimal digits
Machine epsilon 2^-52One operation errs by at most ~1.1e-16 relatively
Binary fractions only0.1, 0.2, 0.3 are all inexact
Above 2^53Consecutive integers are not all representable

The standard reference for all of this remains David Goldberg's "What Every Computer Scientist Should Know About Floating-Point Arithmetic", ACM Computing Surveys, volume 23, issue 1, 1991, pages 5 to 48.

The row to carry forward is the fourth. A single operation is correctly rounded and extremely accurate, which makes floating point sound safe. The next lesson shows what happens across many operations, and specifically what happens when two nearly equal numbers are subtracted, where the relative bound above stops protecting anything at all.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Why can 0.1 not be represented exactly as a double?
    • Because the significand has only 52 stored bits
    • Because its denominator has a factor of five, and no power of two does, so its binary expansion repeats forever
    • Because it is smaller than machine epsilon
    • Because decimal literals are parsed with reduced precision
  2. Why does adding 1.0 to a double near 1e16 leave it unchanged?
    • Because the addition overflows the significand
    • Because 1.0 is smaller than machine epsilon
    • Because the spacing between representable doubles there exceeds 1, so the correctly rounded result is the original number
    • Because the compiler optimises the addition away
  3. What exactly does the IEEE 754 correctly-rounded guarantee cover?
    • A single arithmetic operation, with a bounded relative error
    • Any sequence of operations, with the errors cancelling
    • All operations, with a bounded absolute error
    • Only addition and subtraction
  4. Why does NaN compare unequal to itself?
    • Because its bit pattern is not fixed by the standard
    • Because comparison with NaN raises an exception
    • Because it is stored as a subnormal value
    • Deliberately: it makes x != x a reliable NaN test and stops undefined values comparing as equal
  5. Which of these should not use floating point?
    • A physical measurement from a sensor
    • A monetary amount subject to legal rounding rules
    • A statistical average over sampled data
    • A geometric coordinate in a simulation

Related lessons

Computer Science
advanced

Conditioning and Stability: Whose Fault Is the Error

Two different things can make a numerical answer inaccurate: the problem may amplify any input perturbation, or the algorithm may introduce its own. They are separate quantities, one belongs to the problem and one to the code, and only the second is fixable. This lesson separates them and shows what follows.

8 steps·~12 min
Computer Science
advanced

Comparing Floats, and Testing Code That Uses Them

Equality fails on floats, and both standard replacements fail too: absolute tolerance breaks at scale, relative tolerance breaks near zero. This lesson works through why each fails, what the combined form actually does, and how to choose a tolerance from the problem rather than copying a magic constant.

8 steps·~12 min
Computer Science
advanced

Cancellation: Where the Digits Actually Go

Every operation is correctly rounded, yet results can be wrong in the first digit. The reason is that subtracting nearly equal numbers is exact and still catastrophic: it does not create error, it promotes error that was already there. This lesson builds that mechanism and the summation techniques that recover the lost accuracy.

8 steps·~12 min
Programming
advanced

Refs: A Branch Is a File Containing a Hash

Hashes are unusable by hand, so Git names them. A branch is not a copy of anything, not a directory, and not a container for commits: it is a forty-character file. Once that is clear, checkout, reset, detached HEAD and the reflog stop being separate concepts and become one operation on one kind of file.

8 steps·~12 min