Game Development Community

Floating-point precision and why we can't get around it

by Daniel Buckmaster · in General Discussion · 04/11/2013 (3:13 pm) · 15 replies

I just wanted to put this idea, finally, to bed. Often, people ask how they can extend the range of their game world in Torque beyond the mythical 10km-from-origin limit. Often, they are told that they can scale down all their objects by, say, 10x, so the 10km limit becomes a 100km limit. While this seems like a great idea, it's sadly not the case.

When you scale down your distances and sizes, you make the tiny errors that exist in floating point numbers more visible. Imagine you're a human, and a small rock at your foot jumps a millimetre to the left. You don't notice, it's a tiny error. But now imagine you're an ant next to the rock. Suddenly, the rock moves half your body length!

Here are some helpful references from around the web:

For the rest of this post, I'm going to use the T2D console to demonstrate this happening. It's not essential reading. I just want everyone to understand: there's no free lunch when it comes to floating-point numbers. Scaling them doesn't work.

Okay?

Okay.

Unfortunately, floating-point numbers' greatest strength is also their weakness. While they can represent extremely large values as well as extremely small ones, they don't do well at mixing the two, and this is why errors start to turn up as you travel farther and farther from the world origin. Let's start simple, by manipulating a float in T2D's console. We'll add one millimetre at a time to an initial 10 metres displacement. Say, for example, you have a very slow-moving car.
==>$x = 10;
==>$x += 0.001;
10.0010004
==>$x += 0.001;
10.0020008
==>$x += 0.001;
10.0030012
==>$x += 0.001;
10.0040016
I've omitted all the echo() calls for brevity. The thing you should understand from this log is that floating-point numbers are never accurate. It's just often the case that we don't notice the inaccuracy. The errors, the very last digits, are on the order of micrometres, which you're never going to notice on-screen. Who cares if the engine is a micrometre off?

Let's try some larger numbers.
==>$x = 2000;
==>$x += 0.001;
2000.00098
==>$x += 0.001;
2000.00195
==>$x += 0.001;
2000.00293
==>$x += 0.001;
2000.00391
==>$x += 0.001;
2000.00488
==>$x += 0.001;
2000.00586
==>$x += 0.001;
2000.00684
After adding 7 millimetres, we achieve an actual displacement of 6.8 millimetres. That's not the end of the world, but you can imagine how this can build up. These errors manifest themselves as simulation instability as well as rendering artifacts - objects jittering and so on. Because these errors show up in relative calculations as well - for example, two objects close together but far from the origin:
==>echo(5000.018 - 5000.017);
0.0009765625
Still not too bad, but you can imagine this is a bit unsettling for detailed physics or collision detection calculations.

So, let's try and increase the accuracy by scaling down. We'll divide everything by 10, so 200 units is 2000m, the same value we were working with before. Now, to add one millimetre, we have to add 0.0001:
==>$x = 200;
==>$x += 0.0001;
200.000107
==>$x += 0.0001;
200.000214
==>$x += 0.0001;
200.00032
==>$x += 0.0001;
200.000427
==>$x += 0.0001;
200.000534
==>$x += 0.0001;
200.000641
So after adding 6mm, we end up with a 6.4mm displacement. Though the error is a smaller number than before (we're off by 0.00004 instead of by 0.0002), it looks bigger because everything around it is smaller. In fact, the visual error is worse than before! (That's just bad luck: in general, scaling down doesn't make you less accurate, but for these numbers, the errors happened to work out that way.)

The idea that you can get better floating-point resolution by scaling everything down is similar to the idea that you can give your television screen a higher resolution by sitting closer to it - it doesn't work that way. Sorry to harp on about this. It's one of the most persistent game engine myths I've seen, in other forums/engines as well as here, and I've wanted to go all Mythbusters on it for a while.

Oh, and don't think this is a Torque-specific problem. I just used the T2D console because I had it handy. This is a problem that happens to all applications that use floats.

About the author

Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!


#1
04/11/2013 (4:22 pm)
... *trembles* ... maths ... science ... gah ...

But nice explanation in fairly idiotproof terms ... until someone builds a better idiot.
#2
04/11/2013 (6:05 pm)
@dB,
Awesome, thanks for the insight.
#3
04/12/2013 (10:29 am)
We need to create a paged virtual coordinate space with higher precision - say, 128 bit. That's my attempt at building a better idiot - but it's how memory works in *nix systems as far as I can understand. 32 bit Linux can address 8 GB of RAM because the memory is virtualized - no Windows 32 bit address space.

Get on it Danny Boy! I have faith in you!
#4
04/12/2013 (12:54 pm)
I think the guys doing really zoomed in fractals are using fixed point math for precision. At some point I was going to look into this for my fractal explorer, but that got put off for other priorities. Is fixed point something we should consider?

Also, how does changing the variables representing the position affect the engine? Last I heard it was NOT trivial to change this.
#5
04/12/2013 (3:53 pm)
Richard: I find your abundance of faith [in my abilities] disturbing ;P.

Demolishun: We probably shouldn't consider anything... extremely large ranges with high precision are a bit of a niche requirement still. I mean, if you wanted to make Skyrim without any tricks like recentering each zone you enter, sure. Or a rocket sim like Jack. But it's not an issue for most cases, and I doubt we should be trying to work around it when it's not usually an issue.

I wrote the post to counter that persistent scaling idea, not to complain that floats aren't good enough!

I think Jack has the right idea, though, of doing very domain-specific calculations like his in a separate module using high-precision maths, then letting T3D work out the rendering in regular precision.
#6
04/12/2013 (4:41 pm)
We should create a 1024 bit floating point data type and use that.

Or we could use that old recentering hack.

Actually, I like throwing ludicrous but plausible solutions out there - someone might like the idea and actually do something with it. Most of them are way too much work, but someone might find a slick way to pull them off....
#7
04/14/2013 (12:06 pm)
The first time I found out about this I was making a small program to help with my homework (I was home-schooled so I was allowed to think outside the box...)
I was trying to get it to find if three points on a graph were collinear but that dealt with tiny amounts.
I ended up having to floor all my numbers to get it to work.
I guess keeping your numbers as close to zero as possible provides the best accuracy.

#8
04/14/2013 (2:26 pm)
@dB,
You make a good point on need.

@all,
How does Morrowind, Oblivion, and Skyrim do terrain paging? I really would like to be able to do that at some point.
#9
04/14/2013 (2:30 pm)
Richard: oh, definitely. While we're doing that, I guess we should mention arbitrary-precision arithmetic. [even more ludicrous]Haskell has that... what we do is write a Haskell calculator, then compile it to C, then paste the C code into T3D...[/even more ridiculous]

Alpha-Kand: it's disappointing, right? I'm pretty sure somewhere in TGE I saw a little function that measured flots against some tiny delta that was usually smaller than the error, so two numbers with a very tiny difference were treated as equal.

Demolishun: I'd love to know as well :P.
#10
04/14/2013 (8:37 pm)
Quote:somewhere in TGE I saw a little function that measured floats against some tiny delta that was usually smaller than the error, so two numbers with a very tiny difference were treated as equal.

That sounds handy but I'm not sure what to search for to find a function like that.
I was programming in raw C++ so while it was technically C++'s fault, it blamed me for my "oversight". 2 + 2 may or may not equal 4.
#11
04/14/2013 (9:01 pm)
Maybe search for 'epsilon', I think that was the accuracy constant.
#12
04/18/2013 (12:53 pm)
Wait, wait - we could use a two gigabit fixed precision format in base 11.

As for the net-immerse-derived engines, I'm pretty sure they just divided the terrain into "cells" and streamed them in as you moved from one to the next. You could simply center the current cell and drop the player at the edge, or you could move the world around the player. The interior instances would be much easier as you could load them at once or use zones or triggers to load parts as needed. Since the transitions from "outside" to "inside" were handled by a barrier the whole thing is pretty doable in T3D.

It's so simple. <cough>
#13
04/18/2013 (3:24 pm)
Yeah, I was thinking about the loading thing as well. It would have to be seamless.
#14
08/18/2013 (4:50 pm)
This is actually the exact problem the java online game Runescape has. The smaller players verts jitter around when animated and larger structures are more smooth.
#15
08/19/2013 (6:43 am)
Thinking about the Elder Scrolls game a little more I'm convinced that they stuck with single-player games because they move the world around the player. Elder Scrolls Online should be interesting....