Why large worlds shake
Move an object far enough from the origin in a 3D engine and it starts to shake. Not because anything is broken, but because a 32-bit float stops being able to represent the difference between one frame’s position and the next.
This is one of the few problems in game development where the constraint is arithmetic rather than design, and the arithmetic is unusually easy to reason about.
Precision is relative, not absolute
A single-precision float has 24 bits of significand, giving roughly seven decimal digits of precision — but those digits are relative to the magnitude of the number, not absolute.
Which means the spacing between representable values grows with distance from zero:
| Distance from origin | Approx. spacing between floats |
|---|---|
| 1 m | 0.00000012 m |
| 1,000 m | 0.00006 m |
| 100,000 m | 0.008 m |
| 1,000,000 m | 0.06 m |
| 10,000,000 m | 1 m |
At ten thousand kilometres from the origin, consecutive representable positions are about a metre apart. Sub-metre motion is not slow — it is unrepresentable. The object snaps between lattice points, which is what the shaking is.
None of that needs to be taken on faith. The gap to the next representable float is one increment of the underlying bit pattern, so you can read it directly:
static float Spacing(float x)
{
int bits = BitConverter.SingleToInt32Bits(x);
return BitConverter.Int32BitsToSingle(bits + 1) - x;
}
Spacing(1f); // 1.19e-07
Spacing(1_000f); // 6.10e-05
Spacing(100_000f); // 0.0078
Spacing(10_000_000f); // 1.0
Those are the table values, recovered from the format itself rather than measured. The exponent advances one step per octave, and the spacing is 2e−23 throughout.
Why it looks like jitter specifically
Rendering makes it worse than the raw numbers suggest. A vertex position is transformed by model, view and projection matrices, each multiplication compounding rounding error. Two vertices that should stay rigidly attached round differently and the mesh visibly deforms.
Physics compounds it again. Integrators accumulate small increments, and when the increment is smaller than the spacing between representable values, adding it changes nothing at all. Velocity becomes a number the position cannot act on.
The fixes, in order of how much they cost
Floating origin. Keep the camera near zero and translate the world around it. Precision is best near the origin, so put the thing being looked at there. Cheap and effective, and it handles the rendering half completely.
Spatial partitioning with local coordinates. Divide the world into sectors, store positions relative to a sector origin, and keep the sector index as an integer. Precision becomes constant everywhere. This is the standard solution for large worlds and it is why the technique appears in essentially every open-world engine.
Doubles. A double has 53 bits of significand, moving the metre-spacing threshold out past the orbit of Neptune. It also doubles memory bandwidth for position data and is not universally supported on the GPU side. A common compromise is doubles on the CPU for simulation, single-precision relative offsets for rendering.
Practical note: the fix is almost always about the origin rather than the number type. If you can arrange for the numbers that matter to stay small, single precision is sufficient for most worlds, and it stays cheap.
// Authoritative positions stay in double. Only the camera-relative
// result is ever narrowed to float.
Vector3d worldPos; // absolute, double
Vector3d cameraPos; // absolute, double
Vector3 renderPos = (Vector3)(worldPos - cameraPos);
The ordering is the whole trick. Subtract first at double precision, narrow second. Narrow first and you have already thrown away the bits the subtraction needed.
The subtraction problem
There is a second failure that shows up even at modest distances. Subtracting two nearly equal floats destroys precision — the leading digits cancel and what remains is mostly rounding error. This is catastrophic cancellation, and it appears wherever you compute a small difference from two large quantities.
Which is exactly what a relative position is. Two objects at 50 km from the origin, 10 cm apart, produce a difference vector where much of the precision has already been consumed by the coordinates themselves. Collision code that looks fine near the origin can become unreliable far from it, and the symptom is intermittent rather than absolute.
Where else this shows up
Anywhere coordinates get large. Geospatial pipelines hit it constantly: projected coordinates in a national grid can run to seven digits before the decimal point, leaving very little for the fractional part in single precision. It is one of several reasons geospatial libraries default to double precision throughout.
Orbital work has the same shape at larger magnitudes, which is part of why relative-state formulations exist — expressing an encounter as a small displacement between two objects rather than as the difference of two large absolute positions.
The general principle is worth carrying: precision is relative, so keep the numbers that matter small. Almost every real fix for this class of problem is some version of choosing a better origin.
References
- D. Goldberg. “What Every Computer Scientist Should Know About Floating-Point Arithmetic.” ACM Computing Surveys, 23(1), 1991. Still the best single treatment.
- IEEE. IEEE Standard for Floating-Point Arithmetic (IEEE 754-2019). Where the 24- and 53-bit significands come from.
- C. Ericson. Real-Time Collision Detection. Morgan Kaufmann, 2004. The chapter on numerical robustness covers cancellation in geometric predicates.
- N. J. Higham. Accuracy and Stability of Numerical Algorithms, 2nd ed. SIAM, 2002. Rigorous treatment of error propagation.
- T. Akenine-Möller, E. Haines, N. Hoffman, et al. Real-Time Rendering, 4th ed. CRC Press, 2018. Depth precision and transform pipelines.