I use the MinGW-w64 g++ compilers 10.2 and 10.3. I have built both myself using https://github.com/niXman/mingw-builds.
There is an oddity with g++ on Windows: the main thread of an application will do floating-point operations with double precision, but additional threads will do them with extended precision. This can be reproduced with this small program ("Test.cpp"):
#include <iostream>
#include <future>
#include <cmath>
#include <string>
#include <thread>
std::string getResult()
{
std::cout << "Thread id: " << std::this_thread::get_id() << std::endl;
std::string result = "Result:\n";
double a, b, c;
int i;
for (i = 0, a = 1.0; i < 10000000; i++)
{
a *= 1.00000001;
b = sqrt(a);
c = pow(a, 0.5);
if (std::abs(b - c) < 1.0e-50)
continue;
result += std::string("a: ") + std::to_string(a) + " - Numbers differ.\n";
}
return result;
}
int main()
{
std::string string1 = getResult();
std::future<std::string> result = std::async(std::launch::async, getResult);
std::string string2 = result.get();
if (string1 != string2)
{
std::cout << "The results are different." << std::endl;
}
else
{
std::cout << "The results are the same." << std::endl;
}
return 0;
}
When compiling it with optimization (!) like this:
g++ -o Test.exe Test.cpp -O2
and executing it, the output is:
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are different.
For me this is a major problem. For security reasons I expect all numerical results to be always the same - no matter whether they are executed asynchronously on different threads or sequentially on the main thread. Otherwise for example my unit tests might fail depending on the execution conditions.
I had posted a message to the MinGW-w64 mailing list: https://sourceforge.net/p/mingw-w64/mailman/message/34896011/. As result of the discussion thread, my solution was to link the object file CRT_fp8.o
.
Modifying the compilation command to g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
(paths might need adjustment) results in all threads doing floating-point operations with double precision. Now the results from different threads won't differ anymore.
This has been a fine solution for me for several years. However, a few weeks ago while playing with different compiler versions I have discovered that the solution with linking to CRT_fp8.o
is not as stable as I had expected.
When compiling with g++ 10.2 and then changing the path to contain the "bin" folder of g++ 10.3, the threads will again produce different results. I can reproduce it here with these console commands:
set path=c:\mingw-w64-10.2\mingw64\bin
g++ -o Test.exe Test.cpp -O2 c:\mingw-w64-10.2\mingw64\x86_64-w64-mingw32\lib\CRT_fp8.o
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are the same.
set path=c:\mingw-w64-10.3\mingw64\bin
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are different.
This is again really bad! If the user of my application by chance has the wrong libraries in his path, he will get different results!!! :-(
Another surprise is that the workaround with CRT_fp8.o
seems to be unnecessary when using only g++ 10.3:
set path=c:\mingw-w64-10.3\mingw64\bin
g++ -o Test.exe Test.cpp -O2
C:\...Path...>Test.exe
Thread id: 1
Thread id: 2
The results are the same.
I have played with the shared libraries in the MinGW-w64 "bin" folder and found out that the behaviour depends on the file "libwinpthread-1.dll". If I copy the file from the g++ 10.2 installation to the g++ 10.3 installation overwriting its own shared library, then the behaviour is again as it has been the last years: linking to CRT_fp8.o is needed to get double precision on all threads.
Is this a bug? A MinGW-w64 bug? Or a libwinpthreads bug? Or a feature? Is my workaround for consistent precision outdated? What is the new workaround?
The described behaviour is a MinGW-w64 bug. Different to the description in the original post, it has nothing to do with g++ versions, but only with MinGW-w64 versions. It has been reported here: https://sourceforge.net/p/mingw-w64/bugs/897/.
What does that mean?
With mingw-builds the MinGW version can be specified when building the compiler. Version 7 is safe, version 8 introduces the described error. So version 7 can be used as a workaround.