Search code examples
c++g++pthreadsprecisionmingw-w64

Why does MinGW-w64 floating-point precision depend on winpthreads version?


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?


Solution

  • 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.