4

The following code produces different output on x86 32bit vs 64bit processors.

Is it supposed to be this way? If I replace it with std::uniform_real_distribution and compile with -std=c++11 it produces the same output on both processors.

#include <iostream>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

int main()
{
    boost::mt19937 gen;
    gen.seed(4294653137UL);
    std::cout.precision(1000);
    double lo = - std::numeric_limits<double>::max() / 2 ;
    double hi = + std::numeric_limits<double>::max() / 2 ;
    boost::random::uniform_real_distribution<double> boost_distrib(lo, hi);
    std::cout << "lo " << lo << '\n';
    std::cout << "hi " << hi << "\n\n";
    std::cout << "boost distrib gen " << boost_distrib(gen) << '\n';
}
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
don bright
  • 1,113
  • 1
  • 11
  • 15
  • You would assume that using the same seed will produce the same result. I would report this as a bug to boost. – gsf Nov 18 '15 at 17:35
  • All I can find in [the docs](http://www.boost.org/doc/libs/1_54_0/doc/html/boost_random/reference.html) is "The Streamable concept allows to save/restore the state of the generator, for example to re-run a test suite at a later time." The table showing the mt19937 generator using `625*sizeof(uint32_t)` space for its state implies that the code uses `uint32_t` regardless of platform. – Peter Cordes Nov 19 '15 at 02:15
  • 1
    Are your 32 and 64bit binaries using the same version of Boost? http://www.boost.org/doc/libs/1_58_0/boost/random/mersenne_twister.hpp says that seeding-from-an-integer changed in 2005, [to address a weakness](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/emt19937ar.html). Hmm, no, that doesn't explain it, since I just tried `g++ -m32` and `g++` on your code, and `diff -u <(./boost-random-seedint64 ) <(./boost-random-seedint32 )` showed the output wasn't the same after the first dozen or so digits. – Peter Cordes Nov 19 '15 at 02:31

1 Answers1

7

BTW, you could have written boost::mt19937 gen(4294653137UL); to avoid seeding with the default seed (5489) in the default constructor. Your code has to loop over all 624 uint32_t elements of the generator's internal state twice.


The generator is always fine, and works the same on any machine. The difference only comes from using floating-point to map it to a uniform_real_distribution.

g++ -m32 -msse2 -mfpmath=sse produces identical output to all the other compilers. 32 vs 64bit is different because 64bit uses SSE for float math, so double temporaries are always 64bit. 32bit x86 defaults to using the legacy x87 FPU, where everything is 80bit internally, and only rounded down to 64bit double when storing to memory.

Note that bit-identical FP results in genral is NOT guaranteed with different compilers even on the same platform.

32bit clang still uses SSE math by default, so it gets identical results to 64bit clang or 64bit g++. Telling g++ to do the same solves the problem. -mfpmath=sse tells it to do calculations with SSE (although it doesn't change the ABI, so floating point return values are still in x87 st(0).) -msse2 tells g++ to assume the target machine supports SSE and SSE2. ( added double-precision to 's single-precision. SSE2 is baseline in the x86-64 architecture, and used to pass/return FP args in the 64bit ABI.)

Without SSE, you could (but don't) use -ffloat-store to precisely follow the C standard and round intermediate results to 32 or 64bits by storing and re-loading them. This adds about 6 cycles of latency to every FP math instruction. (Compared to 3 cycle FP add, 5 cycle FP mul on Intel Haswell.) So don't do this, you'll get horrible code.


debugging steps: I tried it out on Ubuntu 15.10, with g++ 5.2, clang-3.5, and clang-3.8 (from http://llvm.org/apt/).

for i in ./boost-random-seedint*; do echo -ne "$i:\t" ; $i|md5sum ;done
./boost-random-seedint-g++32:           53d99523ca2afeac428eae2c89e69974  -
./boost-random-seedint-g++64:           a59f08c0bc22b8753c474db077b809bd  -
./boost-random-seedint-clang3.5-32:     a59f08c0bc22b8753c474db077b809bd  -
./boost-random-seedint-clang3.5-64:     a59f08c0bc22b8753c474db077b809bd  -
./boost-random-seedint-clang3.8-32:     a59f08c0bc22b8753c474db077b809bd  -
./boost-random-seedint-clang3.8-64:     a59f08c0bc22b8753c474db077b809bd  -

So the only outlier is 32bit g++. All the other outputs have the same hash

Compiler options:

clang++-3.8 -m32 -O1 -g boost-random-seedint.cpp -o boost-random-seedint-clang3.8-32  # and similiar
g++ -m32 -Og -g boost-random-seedint.cpp -o boost-random-seedint32

clang doesn't have a -Og. 32bit g++ with -O0 and -O3 make binaries that give the same output as the one from -Og.


Debugging the 32 and 64bit binaries: their state arrays are identical after the default seed and after the call to gen.seed(4294653137UL).

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    Good point about it not changing the ABI and still using `st(0)`. Also, it's interesting to know that Clang defaults to SSE even in 32-bit mode. I can't understand why people still talk about x86 32-bit code. Much software is still released x86 32-bit and 64-bit mode. I don't understand why. This makes since for ARM where ARMv7 is still quite common but for x86 I don't get it. – Z boson Nov 19 '15 at 08:18
  • @Zboson: I think because some people foolishly bought 32bit windows for computers that could run 64bit. Or maybe it's just because WinXP still isn't dead. Or maybe they have code that makes use of some libraries that can't be recompiled for 64bit for whatever reason? I mean even a huge major app like Skype is only available in 32bit binaries, even for Linux. I totally agree with you: people should stop wasting everyone's time with legacy 32bit stuff. There are ways to make make pointer-heavy data structures not bloated in 64bit code. (e.g. store indices, or allocate mem in the low32). – Peter Cordes Nov 19 '15 at 08:47
  • i have been thinking about this question for a while. The answer is that the users of the projects i work on have old machines laying around. Especially schools. Schools in poor states and countries often have old hand-me-down hardware. That's what it boils down to. Users. – don bright Sep 01 '17 at 23:15
  • @donbright: I think the problem is expecting FP determinism. It's not the users' fault they're running 32-bit software; some other difference in platform could reveal the same problem. (Although if you're lucky, x87 80-bit temporaries are the outlier, and ARM or PowerPC would match the results from 64-bit x86 with SSE2 math as long as you avoid math library functions like `sin()` or `log()`, because + - * / and sqrt are all required to be "correctly rounded" to the last ulp.) – Peter Cordes Sep 01 '17 at 23:32