为什么此计算在 boost::thread 和 std::thread 中给出不同的结果?
Why does this calculation give different result in boost::thread and std::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);
}
这似乎是因为 boost::thread
默认情况下对浮点数学使用 53 位内部精度,而主线程使用 64 位精度。如果在 boost::thread
创建后 FPU 单元的状态用 _fpreset()
重置,结果与主线程中的结果相同。
我正在使用 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
问题:
- 为什么会这样?新线程不是应该从父线程继承浮点环境吗?
- 这是编译器或 Boost 中的错误,还是我的预期有误?
这:*reinterpret_cast<unsigned long long*>(&v)
是未定义的行为,因为 v
不是 unsigned_long_long
。如果要将 double
的二进制表示复制为整数类型,请使用 memcpy()
。请注意,即使使用 memcpy()
,它的实现也定义了二进制表示的外观,但您可以保证可以“加载回您保存的内容”。仅此而已。
也就是说,你需要一个更好的编译器。
This seems to happen because boost::thread is by default using 53-bit internal precision for floating point math, while the main thread is using 64-bit precision. If status of FPU unit is reset with _fpreset() after the boost::thread has been created, the result is the same as in the main thread.
这太疯狂了。如果你的编译器针对不同的代码区域使用不同的 FP 单元(即 x87 与 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.0000007966525939409086685
3EAABB3194A6E999
0.0000007966525939409086685
3EAABB3194A6E999
0.0000007966525939409086685
正如@lorro 在 his/her 回答中指出的那样,您违反了 reinterpret_cast
.
中的别名规则
这不是 64 位和 53 位精度 FPU 计算之间的区别,而是 ROUNDING 的区别。两个结果之间的唯一区别在于答案的最低有效位。看起来 boost 的线程启动代码没有正确初始化 FPU 标志,默认舍入模式是向下或切碎,而不是最近。
如果是这种情况,则可能是 boost::thread 中的错误。如果另一个库正在更改 FPU 标志(通过 _controlfp_s 或类似函数),或者如果新线程是线程池的一部分,线程的前一个用户更改了标志,并且池在重新使用线程之前没有重置它们。
区别似乎是 std::thread
实现执行了 _fpreset()
,而 boost::thread
显然没有。如果你改变行
namespace boost { void tss_cleanup_implemented() { } }
至(为清楚起见,格式化了一点):
namespace boost
{
void tss_cleanup_implemented()
{
_fpreset();
}
}
您会看到现在所有值都完全相同 (3EAABB3194A6E99A
)。这告诉我 Boost 不会执行 _fpreset()
。此调用是必要的,因为某些 Windows API 调用会扰乱 C++Builder(32 位)使用的标准 FPU 设置,并且不要将它们设置回原来的状态(这是您可以解决的问题在 Delphi 中也遇到过)。
std::thread
和 boost:thread
都使用 Win32 API 调用来处理线程。
有些事情告诉我你已经预料到了这一点,因此使用 print_number_2()
进行的测试会执行 _fpreset()
.
当这个浮点计算在 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 位精度。如果在 boost::thread
创建后 FPU 单元的状态用 _fpreset()
重置,结果与主线程中的结果相同。
我正在使用 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
问题:
- 为什么会这样?新线程不是应该从父线程继承浮点环境吗?
- 这是编译器或 Boost 中的错误,还是我的预期有误?
这:*reinterpret_cast<unsigned long long*>(&v)
是未定义的行为,因为 v
不是 unsigned_long_long
。如果要将 double
的二进制表示复制为整数类型,请使用 memcpy()
。请注意,即使使用 memcpy()
,它的实现也定义了二进制表示的外观,但您可以保证可以“加载回您保存的内容”。仅此而已。
也就是说,你需要一个更好的编译器。
This seems to happen because boost::thread is by default using 53-bit internal precision for floating point math, while the main thread is using 64-bit precision. If status of FPU unit is reset with _fpreset() after the boost::thread has been created, the result is the same as in the main thread.
这太疯狂了。如果你的编译器针对不同的代码区域使用不同的 FP 单元(即 x87 与 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 在 his/her 回答中指出的那样,您违反了 reinterpret_cast
.
这不是 64 位和 53 位精度 FPU 计算之间的区别,而是 ROUNDING 的区别。两个结果之间的唯一区别在于答案的最低有效位。看起来 boost 的线程启动代码没有正确初始化 FPU 标志,默认舍入模式是向下或切碎,而不是最近。
如果是这种情况,则可能是 boost::thread 中的错误。如果另一个库正在更改 FPU 标志(通过 _controlfp_s 或类似函数),或者如果新线程是线程池的一部分,线程的前一个用户更改了标志,并且池在重新使用线程之前没有重置它们。
区别似乎是 std::thread
实现执行了 _fpreset()
,而 boost::thread
显然没有。如果你改变行
namespace boost { void tss_cleanup_implemented() { } }
至(为清楚起见,格式化了一点):
namespace boost
{
void tss_cleanup_implemented()
{
_fpreset();
}
}
您会看到现在所有值都完全相同 (3EAABB3194A6E99A
)。这告诉我 Boost 不会执行 _fpreset()
。此调用是必要的,因为某些 Windows API 调用会扰乱 C++Builder(32 位)使用的标准 FPU 设置,并且不要将它们设置回原来的状态(这是您可以解决的问题在 Delphi 中也遇到过)。
std::thread
和 boost:thread
都使用 Win32 API 调用来处理线程。
有些事情告诉我你已经预料到了这一点,因此使用 print_number_2()
进行的测试会执行 _fpreset()
.