• 9
• 16
• 15
• 12
• 9

floating point in networking (noob)

This topic is 2157 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Hi
Ive understood floating points (float, double) is non-reliable. Im doing a lock-step realtime strategy game.

But dont i need it? For positioning on the map for example i have armies moving around in realtime. They are not on a specific tile and even if they were i would use some floating point to know the delay until moving to next tile (since i need variable moving speed for different armies).

Lets say i use a lock-step lenght of 100ms (each player can send one commandpackage per step) and each world updates itself due to this commands.
If I use ANY float/double for describing the world it will become unsynched after a while? This seems hard to overcome.

Erik

Share on other sites
I've understood floating points (float, double) is non-reliable ... If I use ANY float/double for describing the world it will become unsynched after a while?
No this is not true. Floating point math is strictly defined by the IEEE-754 standard, and if you run the same code 100 times, you will get the same result 100 times.
You've just got to make sure that every client is running the exact same code...

The problem with reliability is that different compilers (or the same compiler when given slightly different code or settings) may produce different floating-point instructions -- each individual build will be reliable, but two different builds may produce slightly different results.
So a user playing version #1 of your game may generate different results than a user playing version #2 -- this is usually only a problem if you're trying to play back old replay files, or if you support cross-platform play (e.g. Windows is version #1, MacOS is version #2).
However, as long as all of your compilers are set to "strict" floating point mode, then they will all produce the same results.

Share on other sites
If you use 1.0f = 1 second, then there's no problems. Float provides 0.1 accuracy till 100,000 or even 1,000,000. Do you expect someone to play over 27 hours non-stop? (100,000 seconds = 27 hours).

Alternatively, you could store timestamp is 64bit integer. That'll provide good accuracy and you can make it "circular" (if it goes over 64bit limit it just goes back to 0 like nothing happened):
 LSVOID LSE_CALL CTime::Update( LSBOOL _bUpdateVirtuals ) { LSUINT64 ui64TimeNow = GetRealTime(); LSUINT64 ui64Dif; // Handle wrapping gracefully. if ( ui64TimeNow >= m_ui64LastRealTime ) { ui64Dif = ui64TimeNow - m_ui64LastRealTime; } else { ui64Dif = (0xFFFFFFFFFFFFFFFFULL - m_ui64LastRealTime) + ui64TimeNow + 1ULL; } m_ui64LastRealTime = ui64TimeNow; UpdateBy( ui64Dif, _bUpdateVirtuals ); } 
Source: http://lspiroengine.com/?p=378

Share on other sites

If you use 1.0f = 1 second, then there's no problems. Float provides 0.1 accuracy till 100,000 or even 1,000,000.
Ow... careful. What is the representation of 0.1 in powers of two? 1/10 = 1/16 + 1/32 + 1/256 + 1/512 + ...?

You really cannot represent any fractional number in any range, even if it has "only" one digit, and a float has so many. 1.0 works, as do 0.5 and 0.25 and 0.125 and so on, or any sum of these or any power-of-two multiple. Anything else, like 1.1 or 44.7 is kind of nasty to represent.

(p.s.: Also, I know people who play online games 35+ hours before fainting. Yes, this is scary, but such people exist.)

Share on other sites

So a user playing version #1 of your game may generate different results than a user playing version #2 -- this is usually only a problem if you're trying to play back old replay files, or if you support cross-platform play (e.g. Windows is version #1, MacOS is version #2).
However, as long as all of your compilers are set to "strict" floating point mode, then they will all produce the same results.

This is sadly not something to rely on in the real world. While IEEE-754 is rather tightly defined regarding what you have to be able to do, it is not terribly strict about giving you a bit more bits on intermediates at times. Intel in particular had a tendency to do things at 80 bits internally, no matter what. While you could indeed force the precision (and rounding modes, don't forget those) with the floating point control register, you never know where that register has been. DirectX liked to stomp on it in every call back in the DX5 days, might still do that.

Aside from the CPU differences (and the loads of fun getting an AMD and an Intel chip to agree to the bit on results) there's the minor detail that "strict" mode is not exactly a well defined industry standard. While most compilers have some set of options that can turn on such a mode, that still probably won't make them generate 100% identical code, and while you can sort of work around that by using the same compiler on all platforms, good luck finding a visual studio that will spit out code you can link on mac os x.

If you want to be certain, your options are:

Make one of the clients authoritative, and be able to resync the game state if it's found to have drifted
Use fixed point for (absolutely) everything simulation related

A popular RTS game in years past had unsolvable problems with cross platform networking due to FP issues. Most of them were papered over by rounding off every float that went over the wire to ~12 fractional bits. This was not enough, as the AI and/or pathfinding code had some float comparisons that had different numbers of calls to the (synchronized) RNG in the two paths. Eventually one system would go one way, the other would go the opposite way, and the game would explode.

Don't be like that game.

Share on other sites
If you run the same code on all the machines, using the same architecture, and control for variables (like FP rounding modes, 64-bit vs 80-bit double registers, etc) then lock-step simulation with floating point is possible and efficient. Note that some graphics APIs or other libraries may change the floating point control word of the CPU, so you have to test-and-set it at the beginning of your simulation step. Also, trying to do simulation between, say, ARM and x86, will not work deterministically, because the architectures do FP differently, even though both are IEEE compliant.

For timing, you want to use an integer that counts even simulation ticks -- do not use floating point for timing, or your physical behavior will subtly change at the end of an 8-hour game compared to at the beginning. (It can still be deterministic across machines, but I'd prefer to also be deterministic across time.)

Share on other sites
It will not be crossplatform and only use same builds. And use directx as well. No game will be longer then 3 hours for sure. So floating points should be ok then?

Also is there difference in precision for storing something like 13.421 in a float or double? I know double has more bits but does this matter when there is not that many decimals of "real value" anyway?

To know when its time to for next step in my lockstep would i use internal clock? And store that in something like a long (counting milliseconds i guess). My planned timestep will be between 50-100 ms.

Erik

Share on other sites
Number of decimals in base 10 doesn't really relate to number of decimals in base 2, as pointed out by samoth in his example.

And it also depends on how big your integer part of the value is.

I find it more useful to visualize it as markers on a ruler, where you get fewer and fewer markers (less bits) "in between" the whole numbers, the higher your integer part gets.

Share on other sites

Also is there difference in precision for storing something like 13.421 in a float or double? I know double has more bits but does this matter when there is not that many decimals of "real value" anyway?

Here's a specific example to add to what others have said:
1.1 in decimal is approximated as
1.10000002384185791015625 as a 32 bit floating point and
1.1000000000000000888178419700125232338905 as a 64 bit floating point