简体   繁体   中英

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?

Even if you compare the results of sqrt(x) and pow(x,0.5) on one compiler, the results can differ. But the compiler might reasonably inline sqrt , which is obviously simpler than pow . That would mean that if you compile with GCC 10.2 you get an inlined sqrt but when you then run it with a 10.3 runtime DLL. you link to that pow , so you're not even comparing identical versions.

What CRT_fp8.o does is provide an alternative _fpreset function to reset the FPU to an alternative default state - not the 80 bits MinGW default, but 64 bits precision.

Note that MinGW is an impressive attempt to shoehorn GCC into Windows. But GCC is very much a Stallman project in its origins, with a strong Unix assumption.

In the end, the problem might be best avoided altogether by moving to x64. This should use SSE2 for all FP math. And since SSE2 is never 80 bits, you always get 64 bits.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM