当在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);
}
这似乎是因为boost::thread
默认情况下使用53位内部精度进行浮点数学运算,而主线程使用的是64位精度。如果在创建_fpreset()
后使用boost::thread
重置FPU单元的状态,则结果与主线程中的结果相同。
我正在使用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;
}
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E99A
0.0000007966525939409087744
3EAABB3194A6E999
0.0000007966525939409087488
3EAABB3194A6E99A
0.0000007966525939409087744
答案 0 :(得分:5)
这:*reinterpret_cast<unsigned long long*>(&v)
是未定义的行为,因为v
不是unsigned_long_long
。如果要将double
的二进制表示复制为整数类型,请使用memcpy()
。请注意,即使使用memcpy()
,它的实现也定义了二进制表示的外观,但您可以保证可以“加载已保存的内容”。没有更多的AFAIK。
答案 1 :(得分:4)
这不是64位和53位精度FPU计算之间的差异,而是 ROUNDING 的差异。两个结果之间的唯一区别在于答案的最低点。看起来boost的线程启动代码没有正确初始化FPU标志,默认的舍入模式是down或chop,而不是最近的。
如果是这种情况,则可能是boost :: thread中的错误。如果另一个库正在更改FPU标志(通过_controlfp_s或类似的函数),或者如果新线程是线程池的一部分,则该线程的先前用户更改了标志,并且池未重置他们在重用线程之前。
答案 2 :(得分:2)
差异似乎是std::thread
实现_fpreset()
,而boost::thread
显然没有。如果你改变了行
namespace boost { void tss_cleanup_implemented() { } }
to(为了清晰起见,格式化了一点):
namespace boost
{
void tss_cleanup_implemented()
{
_fpreset();
}
}
您将看到所有值现在完全相同(3EAABB3194A6E99A
)。这告诉我Boost没有做_fpreset()
。此调用是必要的,因为某些Windows API调用会破坏C ++ Builder(32位)使用的标准FPU设置,并且不会将它们设置回原来的状态(这也是Delphi中可能遇到的问题)。 / p>
std::thread
和boost:thread
都使用Win32 API调用来处理线程。
有些东西告诉我你已经预料到了这一点,因此使用print_number_2()
进行_fpreset()
的测试。
答案 3 :(得分:1)
要发白,你需要一个更好的编译器。
这似乎发生了,因为boost :: thread默认使用53位内部精度进行浮点数学运算,而主线程使用64位精度。如果在创建boost :: thread后使用_fpreset()重置FPU单元的状态,则结果与主线程中的结果相同。
这太疯狂了。如果你的编译器对不同的代码区域使用不同的FP单元(即x87 vs SSE),你应该用你能找到的最大火力来烧掉那个编译器。
在Linux Mint 17.3上的g ++ - 6.1和clang ++ - 3.8下运行此代码,为每种线程类型提供相同的结果。
#include <thread>
#include <boost/thread.hpp>
#include <cstdio>
#include <cmath>
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);
}
int main() {
print_number();
std::thread t1(&print_number);
t1.join();
boost::thread t2(&print_number);
t2.join();
}
CXX -std = c ++ 14 -O3 -c test test.c -pthread -lboost_thread -lboost_system
3EAABB3194A6E999
0.00000079665259394090866853EAABB3194A6E999
0.00000079665259394090866853EAABB3194A6E999
0.0000007966525939409086685
正如@lorro在他/她的回答中所说,你正在打破reinterpret_cast
中的别名规则。