Floating point

Type a decimal. The steps below are the conversion, done the way you would do it on paper, and the line at the top is what a computer ends up holding.

Loading interactive simulation...
Lesson

The theory — Floating point278 words

A floating-point number is a whole number multiplied by a power of two: (−1)s × 1.f × 2E−1023. The format decides how many bits go to E and how many to f, and that single split fixes both how far the format reaches and how finely it can tell two numbers apart.

What each symbol means

s
the sign bit, 0 for positive and 1 for negative. It sits on its own rather than being folded into the number, which is why the format has both a +0 and a −0.
E
the exponent field, held with 1023 added to it so it can run negative without needing a sign of its own. It chooses which range you are in, and the range is what decides how far apart the neighbours are.
f
the fraction: 52 bits following a leading 1 that is never stored, because a normalised number always starts with one. Those 52 bits are the entire precision, and the exponent adds none of its own.
Assumes
That the number is finite and normal. Below 2−1022 the leading 1 is dropped and the format keeps going by giving up precision instead, so the smallest positive double is 4.94 × 10−324 and carries a single bit of significand.
Breaks when
In two places, both of them in real code. Comparing two computed values with = fails whenever they were reached by different routes, because different routes round differently. And a long sum depends on the order it is added in: run a list forwards and backwards and the totals differ. Adding the smallest terms first loses the least.

Eight of the thousand 🖖

Of the numbers 0.001 to 1.000, exactly eight are held with no error at all: 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875 and 1. A binary fraction is a sum of halves, quarters, eighths and so on down, and a denominator of 1000 carries a factor of 125 that no power of two will ever clear. Type 0.375 and the tool calls it exact. Type 0.376 and it does not.

0.1 + 0.2 lands exactly halfway 🖖

The two stored values add up to 0.3000000000000000166533453693773481063544750213623046875, and that is the precise midpoint between the two doubles on either side of 0.3. The addition itself is faultless. A tie has to be broken somehow, and the rule is to take the neighbour whose last bit is even: the double below 0.3 ends its fraction on an odd bit and the one above ends on an even one, so the sum goes up. That is where the tail on 0.30000000000000004 comes from.

Fewer bits, right answer 🖖

Press binary32 with the same two numbers. 0.1 + 0.2 comes to 0.300000011920928955078125, which is exactly what 0.3 stores as in that format. The famous failure needs the sum to land on a tie, and in 24 bits it lands a quarter of a gap short of one, so it rounds back to the number you were expecting. Half the precision, and the answer you wanted.

The gap doubles at every power of two 🖖

Anywhere between 0.0625 and 0.125 the distance from one representable number to the next is 1.39 × 10⁻¹⁷, and it does not vary within that range at all. Cross into the next range up and it doubles. The staircase in the chart is exactly that, one step per power of two, which is why the same format can hold 10⁻³⁰⁰ and 10³⁰⁰ and cannot hold 9007199254740993.

Problem solved in full

  1. A payments system holds amounts in pounds as binary64. Show that £0.10 and £0.20 are both stored slightly high, that adding them does not give £0.30, and say what a payments engineer does about it.

    1. 0.1 in base two is 0.0001 followed by 1001 repeating for ever. A tenth needs a five in its denominator and a binary fraction has nothing but twos, so the expansion never closes.

    2. 53 binary digits are kept and the rest are rounded away. Here they round upwards, so the stored value sits above 0.1 by 5.55 × 10⁻¹⁸, and the rounding step on the page shows that direction as an arrow.

    3. 0.2 is those same 53 digits with the exponent one higher, so its error is exactly double: 1.11 × 10⁻¹⁷. Put 0.2 in the box and compare the two error cards.

    4. Added together, the two stored values land exactly on the midpoint between the two doubles either side of 0.3. A tie goes to whichever neighbour ends in an even bit, and that is the one above.

    5. So what comes back is 0.3000000000000000444089209850062616169452667236328125, and every later calculation carries it.

    6. Now type 10 and 20 instead of 0.1 and 0.2. Both are whole numbers well below 2⁵³, both are held exactly, and 10 + 20 is 30 with nothing lost anywhere.

    Answer

    Hold money as a whole number of the smallest unit, pence rather than pounds, and the arithmetic is exact because every integer up to 2⁵³ is exact. The only rounding left is the one you write yourself, at the moment you choose, where you can point at it. The tool shows both halves in ten seconds: type 0.1 and the error card is not zero, type 10 and it is.

Example problems

  • 0.1 - 0.1 is kept as 0.1000000000000000055511151231257827021181583404541015625, and 0.2 is high by twice as much. Their sum lands exactly halfway between the two numbers either side of 0.3, so the tie-break rule decides where it goes, and it goes up.
  • 0.375 - The base-two line stops after three digits instead of running on for ever, and the tool calls the value exact. 0.375 is 3/8, and eighths are what a binary fraction is made of. Change the last digit to 0.376 and the expansion never ends.
  • 0.1 in 32 bits - The same 0.1 in 24 bits of significand instead of 53: kept as 0.100000001490116119384765625, and the tool reports 7.22 decimal digits. Add 0.2 here and the answer is 0.300000011920928955078125, which is exactly what 0.3 stores as in this format.
  • 2⁵³ + 1 - Above 2⁵³ the gap reaches 2, so every second whole number is missing. 9007199254740993 sits exactly between two of them and the tie-break sends it down to 9007199254740992, an error of −1. Adding 1 to that changes nothing, for the same reason.
  • 10¹⁶ + 1 - 10¹⁶ is held exactly and so is 1, and 10¹⁶ + 1 comes back as 10¹⁶. The gap here is 2, so 10000000000000001 is a tie between two neighbours, and the even-last-bit rule that pushed 0.1 + 0.2 upwards pushes this one back down.