3
votes

I'm asking this question because I'm trying to achieve bitwise (hash) equality between Visual Studio 2017 (cl.exe) and gcc 5.4.0. The problematic function makes use of sin() and cos(). All variables are double, and FMAD is also relevant.

I've been reading extensively on SO and the web about floating point determinism, reproducibility, and lock-stock MP game design. I'm aware that single-compiler, single-build determinism is not hard, but I am attempting 2-compiler, single-build determinism.

Efficiency is not a concern here. I just want the results to match.

I ask because I hope to narrow my concerns for what to test/try.

Are these things relevant for x86_64 processors and builds?

  1. functions that control the x87 fpu
  2. XPFPA_{DECLARE,RESTORE,etc}
  3. "<"fpu_control.h>, _FPU_SINGLE, _FPU_DOUBLE, etc.
  4. _controlfp_s(), _PC24, _PC53, _PC_64

I ask because I have read that platforms with SSE (x86_64) default to using SSE for floating point, so fpu control functions should be irrelevant?

I have found this and this to be most informative. This MSDN article says setting the floating point precision mask is not supported on x64 arch. And this SO post says SSE has fixed precision.

My testing has shown that /fp:{strict,precise,fast} are not changing the hashes. Neither is optimization level. So I'm hoping to narrow my focus to sin, cos.

1
gcc and MSVC use different standard libraries, and thus potentially different implementations of trig functions. On top of this different hardware implementations of SIMD can give different answers. You may have more luck using the same 3rd party maths library (e.g. MKL) on both platforms. In my experience you'll be lucky if you ever consistently get exact bitwise results across both platforms. Perhaps have a read of this - software.intel.com/en-us/articles/…virgesmith
An mcve demonstrating the issue would be useful.rustyx
I think if you want sin/cos be the same, then you'll need to use the same implementation (i.e. find/create an implementation of these functions, and include them into your project). It's because SSE doesn't have sin/cos, so these are implemented by some algorithm. Different libraries will give you different guarantees (the only exception is if they guarantee the most precise result with the same rounding).geza
You may find this Random ASCII blog entry useful.user694733
for example, crlibm provides correctly rounded results for sin/cos (I haven't used this library)geza

1 Answers

1
votes

Most floating point functions have to perform rounding one way or an other. The C/C++ standard is rather vague on the subject, and IEEE conformance is not strict enough on trigonometric functions. Which means that in practice it is useless to try to squeeze correct rounding out of your compilers default math implementation in a portable way.

For instance, the libm implementation (used by gcc) of sin/cos is written in assembly and the algorithm is different for different architectures and most probably depends on the version of the library.

You therefore have two possibilities:

  • implement your own sin/cos using only floating point operations with exact rounding (fused multiply-accumulate + Taylor series)
  • use a 3rd party library with strong rounding considerations

I personally use the MPFR library as a gold standard when dealing with rounding errors. There will be a runtime cost, although I never tried to benchmark it against libm performance.


Custom Implementation

Note that if you decide to implement it yourself, you need to choose the rounding mode and inform the compiler that it matter to you.

In C++ it is done this way:

#include <cfenv>
#pragma STDC FENV_ACCESS ON
#pragma STDC FP_CONTRACT OFF

int main(int, char**) {
    ...
    if(!std::fesetround(FE_TONEAREST))
        throw std::runtime_error("fesetround failed!");
    ...
}