为什么这个计算在boost :: thread和std :: thread中给出不同的结果?

vll*_*vll 14 c++ c++builder floating-accuracy boost-thread

执行此浮点计算时boost::thread,它会产生与std::thread在主线程中或主线程中执行时不同的结果.

void print_number()
{
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    printf("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v);
}
Run Code Online (Sandbox Code Playgroud)

这似乎是因为boost::thread默认情况下使用53位内部精度进行浮点数学运算,而主线程使用的是64位精度.如果在创建_fpreset()后重置FPU单元的状态boost::thread,则结果与主线程中的结果相同.

我正在使用Embarcadero C++ Builder 10.1(编译器bcc32c版本3.3.1)和Boost 1.55.0.我的环境是Windows 7,我正在构建32位Windows目标.

工作范例:

#include <tchar.h>
#include <thread>
#include <boost/thread.hpp>
#include <cstdio>
#include <cmath>
#include <cfloat>

namespace boost { void tss_cleanup_implemented() {} }

void print_number()
{
    double a = 5.66;
    double b = 0.0000001;
    double c = 500.4444;
    double d = 0.13423;
    double v = std::sin(d) * std::exp(0.4 * a + b) / std::pow(c, 2.3);

    // Edit:
    // Avoiding the undefined behaviour by a reinterpret_cast, as
    // mentioned in some answers and comments.
    unsigned long long x;
    memcpy(&x, &v, sizeof(x));

    printf("%llX\n%0.25f\n", x, v);
}

void print_number_2()
{
    // Reset FPU precision to default
    _fpreset();
    print_number();
}

int _tmain(int argc, _TCHAR* argv[])
{
    print_number();

    std::thread t1(&print_number);
    t1.join();

    boost::thread t2(&print_number);
    t2.join();

    boost::thread t3(&print_number_2);
    t3.join();

    getchar();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E999
0.0000007966525939409087488
3EAABB3194A6E99A
0.0000007966525939409087744
Run Code Online (Sandbox Code Playgroud)

题:

  • 为什么会这样?是不是一个新的线程应该从父线程继承浮点环境?
  • 这是编译器或Boost中的错误,还是我的期望错了?

lor*_*rro 5

这:*reinterpret_cast<unsigned long long*>(&v)是未定义行为v不是unsigned_long_long.如果要将a的二进制表示复制double到整数类型,请使用memcpy().请注意,即使使用memcpy(),它的实现也定义了二进制表示的外观,但您可以保证可以"加载已保存的内容".没有更多的AFAIK.

  • @ Ville-ValtteriTiittanen这个答案只是指出代码中的一个错误,并不意味着这个错误是你问题的根源.修复错误是进行任何进一步调查的先决条件,即使它似乎没有"改变"任何东西. (2认同)

Rud*_*uis 2

区别似乎在于实现std::thread执行了_fpreset(),而boost::thread显然没有执行。如果你改变线路

namespace boost { void tss_cleanup_implemented() { } }
Run Code Online (Sandbox Code Playgroud)

至(为了清晰起见,稍微格式化一下):

namespace boost 
{ 
    void tss_cleanup_implemented() 
    { 
        _fpreset(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

您将看到现在所有值都完全相同 ( 3EAABB3194A6E99A)。这告诉我 Boost 不执行_fpreset(). 此调用是必要的,因为某些 Windows API 调用会弄乱 C++Builder(32 位)使用的标准 FPU 设置,并且不会将它们设置回原来的状态(这也是您在 Delphi 中也可能遇到的问题)。

两者std::threadboost:thread使用 Win32 API 调用来处理线程。

有些东西告诉我你已经预料到了这一点,因此测试使用print_number_2()_fpreset().