Skip to content

Numbers That Aren't Whole

A machine that counts in whole steps still has to carry half a pixel. The flexible answer is to let the point float, and it is never exact.

Every number so far has been a whole one. The moment you write a game, you need the in-between: a ship that drifts half a pixel a frame, a ball at two-thirds speed.

The machine counts in whole steps. So how do you give it a fraction?

There are two answers. This unit takes the flexible one, and finds the problem with it. The next unit takes the answer games used.

Let the point float

The first answer is to let the decimal point float. It sits wherever the number needs it, close up for tiny fractions and far out for huge values.

It is flexible, and it is what most languages hand you without being asked. Type a fraction and it works:

SHOW 1 / 3
Output
0.33333333

Look at what came back. Not a third, but a run of threes that stops.

How many threes you get depends on the machine, and every machine stops somewhere. A third has no exact form in tens, and there is only so much room to hold one.

Floating-point is only ever an approximation.

Why it can never be exact

You might think that is a problem only for awkward numbers like a third. It is not, and the reason goes back to Counting in Twos.

The places to the left of the point double as you go: 1, 2, 4, 8. The places to the right halve: a half, a quarter, an eighth, a sixteenth. Every fraction the machine holds exactly is built by adding some of those together.

A half is one of them. So is three quarters, which is a half and a quarter. Fine so far.

Now try a tenth. There is no run of halves and quarters and eighths that comes to exactly 0.1. It recurs, on and on, the way a third does in tens:

0.1 in tens   0.1                 exact
0.1 in twos   0.000110011001100…  recurring

So 0.1 is perfectly exact when you write it in tens, and not exact at all inside the machine.

The machine stores the closest it can hold, which is a hair out. Add a few of those hairs together and the error shows up in your answer. This catches people out in every language there is, which is why you never test two floating-point numbers for being exactly equal.

And on these machines, it was slow

There is a second problem, and for games it mattered more.

The processors in these machines have no hardware for floating-point. There is no instruction that adds two fractions. Every floating-point sum happens in software, worked out step by step from simpler operations.

That is flexible and it is slow. Far too slow to do dozens of times a frame while a game is running.

When it’s wrong, see why

  • A float was not exactly the value you expected. It stores the closest it can, and that includes tidy-looking values like 0.1.
  • Two floats that should be equal were not. Each was stored a hair out, and the hairs differed. Compare them for being close enough instead.
  • Small errors grew. Every sum adds a little more. The more arithmetic a value has been through, the further it has drifted.

What you’ve learnt

  • A floating-point number lets the point sit wherever the value needs it.
  • It is flexible, and most languages give it to you free.
  • It is approximate. The places after the point are halves and quarters, so a value like 0.1 never lands exactly on one.
  • On these machines it is also slow, because nothing in the hardware does it.

What’s next

Flexible and slow is the wrong trade for a game loop. In Unit 5 we stop letting the point float and nail it down, and the arithmetic turns back into the fast kind.