9

I'm writing a small RTS engine in C++ and want to use lockstep synchronisation.

As floating point determinism is something I can't even hope to achieve, I have to use fixed point math.

How deterministically (over different compilers and cpus) are typical operations on unsigned ints defined?

I'm especially interested in division, as that would incur rounding.

lines
  • 141
  • 5
  • 4
    100% deterministic , or at least they should be, if they are not probably the CPU could be broken – Raxvan May 05 '15 at 14:55
  • @Raxvan is right. That is what banks use for money. Typically they scale by 1000, so that they have 10ths of a cent for rounding things up/down. Remember, operations will truncate at the end of the int. – J. A. Streich May 05 '15 at 15:23
  • Aren't existing rts games using floating point math for this purpose? – Karl May 05 '15 at 15:39
  • There really isn't any reason to use fixed point numbers, you are just wasting your valuable time on implementing that. If you need to store a real number with high precision just use double, it will surely be able to store whichever value you throw at it. – zoran404 May 05 '15 at 16:04
  • 3
    @zoran404 sure, a double could store it, but then if you do math on it the compiler won't necessarily ensure 100% portable results. For example, it might use x87's 80-bit extended precision and thus give different results. Or it might do illegal reassociation of operations, `a + (b + c) == (a + b) + c` is not necessarily true but some compilers pretend they can do that (ICC by default, GCC if you use unsafe math which is obviously unsafe) – harold May 05 '15 at 16:10
  • 2
    This question does not make sense, if using IEEE floating point, the results are also deterministic. The error is always the same. What exactly are you worried about? That floating point is not exact enough? Also i don't understand what lockstep has to do with floating point vs fixed point... – RedX May 05 '15 at 16:17
  • 100% deterministically, all the time, all of them. This is *mostly* true about floating point as well if you take some precautions. – n. m. could be an AI May 05 '15 at 16:21
  • 2
    @RedX - See https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/. – Josh Kelley May 05 '15 at 16:31
  • @J.A.Streich Banks usually use standardized hardware. – lines May 05 '15 at 16:46
  • @Karl As far as I know, no. – lines May 05 '15 at 16:46
  • "if using IEEE floating point, the results are also deterministic" -- not in the real world, particularly if multiple platforms are involved. Different compilers will optimize control flow differently and perform various strength reductions, and as soon as the order of operations changes even a bit, all bets are off. Also I don't know if there are guarantees about complicated operations like transcendentals across platforms. – StilesCrisis May 05 '15 at 17:00
  • 1
    Not to mention, if you are working on a game, helpful behaviors will occur like DirectX silently changing the CPU rounding modes on you. – StilesCrisis May 05 '15 at 17:03
  • @JoshKelley Thank you for that article i learned a bit and it was very interesting. – RedX May 05 '15 at 17:09
  • I thought that, for many functions, the standards usually did not require the least significant bit to be correct? Admittedly, I don't study this sort of thing much. –  May 05 '15 at 17:35
  • It does, but that isn't guaranteed over several operations. – lines May 06 '15 at 06:59
  • Floating-point computation on different builds of the game, different machines, different compilers and settings, and different architectures are all potentially going to be 'corrrect' but not 'bit-identical'. The recommended settings for VS is ``/fp:fast`` which can introduce optimization differences. Using ``/arch:SSE2`` for x86 (which is also recommended today and is the default for VS 2012+) will get your 32-bit code a lot closer to the x64 native code-gen (which never uses x87). Always best to use a epsilon comparison for float-point numbers. – Chuck Walbourn May 27 '15 at 18:15
  • But it needs to be bit-identical unfortunately (chaos is annoying). I know about epsilon comparison, etc., but that's only useful if you don't need synchronized data. – lines Jun 02 '15 at 22:44

1 Answers1

6

The size of the basic integer types varies on different platforms. You can avoid this problem by using uint32_t and similar types.

Signed integer overflow is undefined behavior, although overflow is well-defined for unsigned integral types (you do the arithmetic modulo 2^N). Even still, you have to watch out for it, because modular arithmetic is usually not what you were trying to do.

I believe the standard once upon a time left open exactly how division is rounded (although I'm not sure if "positive / positive" was ever left open). But I think this is standardized now, and even if not, rounding towards 0 is nearly universal.

But you can use numeric_limits to check. If this documentation is to be believed, the standard does guarantee rounding towards zero.

  • in C++03, division rounding was open. In C++11, It's now standardized, and I _think_ configurable, but I don't recall for certain. – Mooing Duck May 15 '15 at 02:09