Basis Lab

Answers · Where the data lives

Why doesn't 0.1 plus 0.2 come out as 0.3?

In shortA tenth cannot be stored exactly in binary, so the two stored values are already slightly wrong before they are added.

A tenth cannot be written exactly in base two, so the value stored for 0.1 is already not a tenth, and the value stored for 0.2 is already not a fifth. Adding them adds two fixed rounding errors. The result is not random noise, it is the arithmetic consequence of the bits that were kept.

110=0.000110011001100…215=0.001100110011001…2\frac{1}{10}=0.000110011001100\ldots_{2} \qquad \frac{1}{5}=0.001100110011001\ldots_{2}

The rule is the same in every base. A fraction is exact when its denominator divides some power of the base. Ten has factors two and five, so fifths and tenths are exact in base ten and thirds are not. Two has only itself, so halves, quarters and eighths are exact and a tenth is not. A third recurs in decimal and nobody treats that as a defect in the notation; a tenth recurs in binary for exactly the same reason.

FIG 1Which fractions are exact in which base
exact in base tenexact in base two
a half11
a quarter11
a fifth10
a tenth10
a third00
Coded 1 for exact and 0 for recurring. The tenth is the row that causes trouble in practice: it is the unit people write money and percentages in, and it is the most common inexact value in a stored number. The third is exact in neither base, which is the right comparison: recurring is a property of the notation, not an error in the value.

The common eight byte format is 64 bits: one sign, eleven exponent, fifty-two fraction. That is enough for about fifteen or sixteen significant decimal digits. Near 0.3 the exponent is -2, so the gap between neighbouring stored values is 2−2−52=2−542^{-2-52}=2^{-54}.

2−54=118 014 398 509 481 984≈5.55×10−17.2^{-54}=\frac{1}{18\,014\,398\,509\,481\,984}\approx 5.55\times10^{-17}.

Adding the stored 0.1 and the stored 0.2 lands one such step above the stored value nearest 0.3. Printed with enough digits to recover that stored value, the sum is 0.30000000000000004. The gap between that sum and the stored value nearest 0.3 is one step, about 5.55×10−175.55\times10^{-17}, which is the spacing just computed, not a random wobble. An equality test against 0.3 asks whether two different stored values are the same, and the answer is no.

The same failure appears if 0.1 is added to itself three times: the answer is not 0.3, it is slightly more, in about the seventeenth digit. Nothing has gone wrong at the addition step. Three slightly wrong values were added and the total is slightly wrong.

Precision is relative. Near one the gap is about one part in ten thousand million million, and near a million the gap is a million times larger. A single value is accurate to about fifteen digits, far more than a measurement justifies. The trouble is accumulation: many slightly wrong values added together partly cancel and partly do not, so a long total is wrong much earlier than a single value is.

The fixed error

The natural guess is that the machine is imprecise in a vague way, so the error might be different each time or might cancel. It does not. A tenth has a fixed binary expansion and the format keeps a fixed number of bits, so the stored value is the same every run. The same is true of the sum. The only thing that changes with order is how the separate errors accumulate: add many values and the total can differ depending on where you start, but each addition is deterministic.

Money in whole units

For money the repair is not a better fractional type. Store a price as a whole number of pence and every total is exact integer arithmetic. Splitting 100 pence three ways shows the remaining question: rounding each share gives 33, 33 and 33, which adds to 99 and loses a penny. Giving the remainder to one party gives 34, 33 and 33, which adds to 100. The choice is a business rule and must be written down, because the arithmetic alone cannot decide it.

Where it stops

The trouble is not that all fractions are inexact. A half, a quarter and an eighth are exact in binary because their denominators are powers of two, so 0.5+0.25=0.750.5+0.25=0.75 exactly. The trouble is a denominator with a factor other than two, and a tenth has a factor of five. If a value must balance to the penny, whole pence remove the approximation. If it must be compared, a tolerance scaled to the size of the values replaces equality, because the available precision is relative rather than absolute.

Rounding has a separate trap. A value entered as two pounds and fifty five pence can sit just below the halfway point in its stored form and round down to two pounds and fifty four. Rounding to pence at the point of entry, and storing whole pence afterwards, removes that case at the only place it can be removed.

Worth keeping

  • 0.1 is stored as the nearest binary fraction, not as a tenth, so the error exists before any addition.
  • Whole pence totals are exact because they are integers, while fractional money accumulates a deterministic error.
  • Two fractional values that should be equal need a tolerance scaled to their size, not an equality test.

Where this goes next

This question is one step of Bytes, Text and File Formats, a course that works through its subject from the beginning. The page that teaches this part of it is Numbers With Fractions In Them, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. Why is the date off by one day after I save it and read it back?You saved a calendar date as if it were a precise moment, so showing it in another time zone can move it to the day before or after.
  2. Is UTF-8 the same thing as Unicode, or what's the difference?No: Unicode assigns a number to each character, while UTF-8 is a way of writing those numbers as bytes, using one to four bytes per character.
  3. Why does my CSV file fall apart when one of the fields has a comma in it?Because the same comma both separates fields and sits inside the value, so a reader splitting on commas finds one field too many and every later column shifts.
  4. Why do programmers write numbers in hexadecimal instead of just normal numbers?Programmers use hexadecimal because each digit stands for exactly four bits, so one byte is always written as two digits and the columns line up.
  5. How do I make a step safe to run again when I'm not sure whether it already ran?Change the operation so that sending it twice leaves the same stored data as sending it once, instead of relying on remembering not to send it again.
  6. Why does my count come out doubled after the job restarts?Your count doubled because the restart replayed events the saved counter already held: the state and input position were not saved as one snapshot.

Read alongside